/***************************************************
    Arduino TFT graphics library

    This is a standalone library that contains the
    hardware driver, the graphics functions and the
    proportional fonts.

    The larger fonts are Run Length Encoded to reduce their
    size.

    Created by Bodmer 2/12/16
    Bodmer: Added RPi 16 bit display support
 ****************************************************/


#include "TFT_eSPI.h"
#include "TFT_Interface.h"

#ifdef TOUCH
    #ifdef FOURWIRETOUCH
        #include "Extensions/Touch_Drivers/4WiresTouch/defined.h"
    #elif defined XPT2046TOUCH
        #include "Extensions/Touch_Drivers/XPT2046/XPT2046.h"
    #else
        #error Didn't support this Touch yet.
    #endif
#endif

#ifdef HASSPI
    TFT_Interface _com(&SPICOM);
#else
    TFT_Interface _com = TFT_Interface();
#endif

// If it is a 16bit serial display we must transfer 16 bits every time
#ifdef RPI_ILI9486_DRIVER
    #define CMD_BITS (16-1)
#else
    #define CMD_BITS (8-1)
#endif

// Fast block write prototype
void writeBlock(uint16_t color, uint32_t repeat);

// Byte read prototype
uint8_t readByte(void);

// GPIO parallel input/output control
void busDir(uint32_t mask, uint8_t mode);

inline void TFT_eSPI::com_begin(void) {
    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && (HASSPI)
    if (locked) {
        locked = false;
        #ifdef KENDRYTE_K210
        _com.beginTransaction(SPISettings(SPI_FREQUENCY, LSBFIRST, TFT_SPI_MODE));
        #else
        _com.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
        #endif
        CS_L;
    }

    CS_L;
    #endif
}

inline void TFT_eSPI::com_end(void) {
    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && (HASSPI)
    if (!inTransaction) {
        if (!locked) {
            locked = true;
            CS_H;
            _com.endTransaction();
        }
    }
    #else
    if (!inTransaction) {
        CS_H;
    }
    #endif
}

inline void TFT_eSPI::com_begin_read(void) {
    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && (HASSPI)
    if (locked) {
        locked = false;
        #ifdef KENDRYTE_K210
        _com.beginTransaction(SPISettings(SPI_READ_FREQUENCY, LSBFIRST, TFT_SPI_MODE));
        #else
        _com.beginTransaction(SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
        #endif
        CS_L;
    }
    #else
    CS_L;
    #endif
}

inline void TFT_eSPI::com_end_read(void) {
    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && (HASSPI)
    if (!inTransaction) {
        if (!locked) {
            locked = true;
            CS_H;
            _com.endTransaction();
        }
    }
    #else
    if (!inTransaction) {
        CS_H;
    }
    #endif
}

#if defined (TOUCH_CS) && defined (SPI_TOUCH_FREQUENCY)

inline void TFT_eSPI::com_begin_touch(void) {
    CS_H; // Just in case it has been left low

    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && (HASSPI)
    if (locked) {
        locked = false;
        #ifdef KENDRYTE_K210
        _com.beginTransaction(SPISettings(SPI_READ_FREQUENCY, LSBFIRST, TFT_SPI_MODE));
        #else
        _com.beginTransaction(SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
        #endif
    }
    #else
    //_com.setFrequency(SPI_TOUCH_FREQUENCY);
    #endif

    T_CS_L;
}

inline void TFT_eSPI::com_end_touch(void) {
    T_CS_H;

    #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && (HASSPI)
    if (!inTransaction) {
        if (!locked) {
            locked = true;
            _com.endTransaction();
        }
    }
    #else
    //_com.setFrequency(SPI_FREQUENCY);
    #endif
}

#endif

/***************************************************************************************
** Function name:           TFT_eSPI
** Description:             Constructor , we must use hardware SPI pins
***************************************************************************************/
TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) {

    // The control pins are deliberately set to the inactive state (CS high) as setup()
    // might call and initialise other SPI peripherals which would could cause conflicts
    // if CS is floating or undefined.
    #ifdef TFT_CS
    digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
    pinMode(TFT_CS, OUTPUT);
    #endif

    // Configure chip select for touchscreen controller if present
    #ifdef TOUCH_CS
    digitalWrite(TOUCH_CS, HIGH); // Chip select high (inactive)
    pinMode(TOUCH_CS, OUTPUT);
    #endif

    #ifdef TFT_WR
    digitalWrite(TFT_WR, HIGH); // Set write strobe high (inactive)
    pinMode(TFT_WR, OUTPUT);
    #endif

    #ifdef TFT_DC
    digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
    pinMode(TFT_DC, OUTPUT);
    #endif

    #ifdef TFT_RST
    if (TFT_RST >= 0) {
        digitalWrite(TFT_RST, HIGH); // Set high, do not share pin with another SPI device
        pinMode(TFT_RST, OUTPUT);
    }
    #endif

    _init_width  = _width  = w; // Set by specific xxxxx_Defines.h file or by users sketch
    _init_height = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch
    rotation  = 0;
    cursor_y  = cursor_x  = 0;
    textfont  = 1;
    textsize  = 1;
    textcolor   = bitmap_fg = 0xFFFF; // White
    textbgcolor = bitmap_bg = 0x0000; // Black
    padX = 0;             // No padding
    isDigits   = false;   // No bounding box adjustment
    textwrapX  = true;    // Wrap text at end of line when using print stream
    textwrapY  = false;   // Wrap text at bottom of screen when using print stream
    textdatum = TL_DATUM; // Top Left text alignment is default
    fontsloaded = 0;

    _swapBytes = false;   // Do not swap colour bytes by default

    locked = true;        // transaction mutex lock flags
    inTransaction = false;

    _booted   = true;
    _cp437    = true;
    _utf8     = true;

    addr_row = 0xFFFF;
    addr_col = 0xFFFF;

    _xpivot = 0;
    _ypivot = 0;

    cspinmask = 0;
    dcpinmask = 0;
    wrpinmask = 0;
    sclkpinmask = 0;

    #ifdef LOAD_GLCD
    fontsloaded  = 0x0002; // Bit 1 set
    #endif

    #ifdef LOAD_FONT2
    fontsloaded |= 0x0004; // Bit 2 set
    #endif

    #ifdef LOAD_FONT4
    fontsloaded |= 0x0010; // Bit 4 set
    #endif

    #ifdef LOAD_FONT6
    fontsloaded |= 0x0040; // Bit 6 set
    #endif

    #ifdef LOAD_FONT7
    fontsloaded |= 0x0080; // Bit 7 set
    #endif

    #ifdef LOAD_FONT8
    fontsloaded |= 0x0100; // Bit 8 set
    #endif

    #ifdef LOAD_FONT8N
    fontsloaded |= 0x0200; // Bit 9 set
    #endif

    #ifdef SMOOTH_FONT
    fontsloaded |= 0x8000; // Bit 15 set
    #endif
}


/***************************************************************************************
** Function name:           begin
** Description:             Included for backwards compatibility
***************************************************************************************/
void TFT_eSPI::begin(uint8_t tc) {
    init(tc);
}


/***************************************************************************************
** Function name:           init (tc is tab colour for ST7735 displays only)
** Description:             Reset, then initialise the TFT display registers
***************************************************************************************/
void TFT_eSPI::init(uint8_t tc) {
    if (_booted) {

        _com.begin(); // This will set HMISO to input

        inTransaction = false;
        locked = true;


        #ifdef TFT_CS
        // Set to output once again in case D6 (MISO) is used for CS
        digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
        pinMode(TFT_CS, OUTPUT);
        #endif

        // Set to output once again in case D6 (MISO) is used for DC
        #ifdef TFT_DC
        digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
        pinMode(TFT_DC, OUTPUT);
        #endif

        _booted = false;
        com_end();
    } // end of: if just _booted

    // Toggle RST low to reset
    com_begin();

    #ifdef TFT_RST
    if (TFT_RST >= 0) {
        digitalWrite(TFT_RST, HIGH);
        delay(5);
        digitalWrite(TFT_RST, LOW);
        delay(20);
        digitalWrite(TFT_RST, HIGH);
    } else {
        writecommand(TFT_SWRST);    // Software reset
    }
    #else
    writecommand(TFT_SWRST); // Software reset
    #endif

    com_end();

    delay(150); // Wait for reset to complete

    com_begin();

    // This loads the driver specific initialisation code  <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
    #if   defined (ILI9341_DRIVER)
#include "TFT_Drivers/ILI9341_Init.h"

    #elif defined (ST7735_DRIVER)
    tabcolor = tc;
#include "TFT_Drivers/ST7735_Init.h"

    #elif defined (ILI9163_DRIVER)
#include "TFT_Drivers/ILI9163_Init.h"

    #elif defined (S6D02A1_DRIVER)
#include "TFT_Drivers/S6D02A1_Init.h"

    #elif defined (RPI_ILI9486_DRIVER)
#include "TFT_Drivers/ILI9486_Init.h"

    #elif defined (ILI9486_DRIVER)
#include "TFT_Drivers/ILI9486_Init.h"

    #elif defined (ILI9481_DRIVER)
#include "TFT_Drivers/ILI9481_Init.h"

    #elif defined (ILI9488_DRIVER)
#include "TFT_Drivers/ILI9488_Init.h"

    #elif defined (HX8357D_DRIVER)
#include "TFT_Drivers/HX8357D_Init.h"

    #elif defined (ST7789_DRIVER)
#include "TFT_Drivers/ST7789_Init.h"

    #elif defined (R61581_DRIVER)
#include "TFT_Drivers/R61581_Init.h"

    #elif defined (RM68140_DRIVER)
#include "TFT_Drivers/RM68140_Init.h"

    #elif defined (ST7789_2_DRIVER)
#include "TFT_Drivers/ST7789_2_Init.h"

    #endif

    #ifdef TFT_INVERSION_ON
    writecommand(TFT_INVON);
    #endif

    #ifdef TFT_INVERSION_OFF
    writecommand(TFT_INVOFF);
    #endif

    com_end();

    setRotation(rotation);

    #if defined (TFT_BL) && defined (TFT_BACKLIGHT_ON)
    pinMode(TFT_BL, OUTPUT);
    digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
    #else
    #if defined (TFT_BL) && defined (M5STACK)
    // Turn on the back-light LED
    pinMode(TFT_BL, OUTPUT);
    digitalWrite(TFT_BL, HIGH);
    #endif
    #endif
    #ifdef TOUCH
    #if defined XPT2046TOUCH
    xpt.begin();
    #endif
    #endif
}

/***************************************************************************************
** Function name:           setBacklight
** Description:             set the Backlight
***************************************************************************************/
#if defined (TFT_BL)
void TFT_eSPI::setBacklight(uint32_t backlight)
{
    _backlight = backlight;
    analogWrite(TFT_BL, _backlight);
}

uint32_t TFT_eSPI::backlight()
{
    return  _backlight;
}
#endif;


/***************************************************************************************
** Function name:           setRotation
** Description:             rotate the screen orientation m = 0-3 or 4-7 for BMP drawing
***************************************************************************************/
void TFT_eSPI::setRotation(uint8_t m) {

    com_begin();

    // This loads the driver specific rotation code  <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
    #if   defined (ILI9341_DRIVER)
#include "TFT_Drivers/ILI9341_Rotation.h"

    #elif defined (ST7735_DRIVER)
#include "TFT_Drivers/ST7735_Rotation.h"

    #elif defined (ILI9163_DRIVER)
#include "TFT_Drivers/ILI9163_Rotation.h"

    #elif defined (S6D02A1_DRIVER)
#include "TFT_Drivers/S6D02A1_Rotation.h"

    #elif defined (RPI_ILI9486_DRIVER)
#include "TFT_Drivers/ILI9486_Rotation.h"

    #elif defined (ILI9486_DRIVER)
#include "TFT_Drivers/ILI9486_Rotation.h"

    #elif defined (ILI9481_DRIVER)
#include "TFT_Drivers/ILI9481_Rotation.h"

    #elif defined (ILI9488_DRIVER)
#include "TFT_Drivers/ILI9488_Rotation.h"

    #elif defined (HX8357D_DRIVER)
#include "TFT_Drivers/HX8357D_Rotation.h"

    #elif defined (ST7789_DRIVER)
#include "TFT_Drivers/ST7789_Rotation.h"

    #elif defined (R61581_DRIVER)
#include "TFT_Drivers/R61581_Rotation.h"

    #elif defined (RM68140_DRIVER)
#include "TFT_Drivers/RM68140_Rotation.h"

    #elif defined (ST7789_2_DRIVER)
#include "TFT_Drivers/ST7789_2_Rotation.h"

    #endif

    delayMicroseconds(10);

    com_end();

    addr_row = 0xFFFF;
    addr_col = 0xFFFF;
}


/***************************************************************************************
** Function name:           commandList, used for FLASH based lists only (e.g. ST7735)
** Description:             Get initialisation commands from FLASH and send to TFT
***************************************************************************************/
void TFT_eSPI::commandList(const uint8_t* addr) {
    uint8_t  numCommands;
    uint8_t  numArgs;
    uint8_t  ms;

    numCommands = pgm_read_byte(addr++);   // Number of commands to follow

    while (numCommands--) {                // For each command...
        writecommand(pgm_read_byte(addr++)); // Read, issue command
        numArgs = pgm_read_byte(addr++);     // Number of args to follow
        ms = numArgs & TFT_INIT_DELAY;       // If hibit set, delay follows args
        numArgs &= ~TFT_INIT_DELAY;          // Mask out delay bit

        while (numArgs--) {                  // For each argument...
            writedata(pgm_read_byte(addr++));  // Read, issue argument
        }

        if (ms) {
            ms = pgm_read_byte(addr++);        // Read post-command delay time (ms)
            delay((ms == 255 ? 500 : ms));
        }
    }

}


/***************************************************************************************
** Function name:           spiwrite
** Description:             Write 8 bits to SPI port (legacy support only)
***************************************************************************************/
void TFT_eSPI::spiwrite(uint8_t c) {
    tft_Write_8(c);
}


/***************************************************************************************
** Function name:           writecommand
** Description:             Send an 8 bit command to the TFT
***************************************************************************************/
void TFT_eSPI::writecommand(uint8_t c) {
    _com.writeCommand(c);
}


/***************************************************************************************
** Function name:           writedata
** Description:             Send a 8 bit data value to the TFT
***************************************************************************************/
void TFT_eSPI::writedata(uint8_t d) {
    _com.writeData(d);
}


/***************************************************************************************
** Function name:           readcommand8
** Description:             Read a 8 bit data value from an indexed command register
***************************************************************************************/
uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index) {
    uint8_t reg = 0;

    // for ILI9341 Interface II i.e. IM [3:0] = "1101"
    com_begin_read();
    index = 0x10 + (index & 0x0F);

    DC_C;
    writecommand(0xD9);
    DC_D;
    tft_Write_8(index);

    CS_H; // Some displays seem to need CS to be pulsed here, or is just a delay needed?
    CS_L;

    DC_C;
    writecommand(cmd_function);
    DC_D;
    reg = tft_Read_8();

    com_end_read();

    return reg;
}


/***************************************************************************************
** Function name:           readcommand16
** Description:             Read a 16 bit data value from an indexed command register
***************************************************************************************/
uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index) {
    uint32_t reg;

    reg  = (readcommand8(cmd_function, index + 0) <<  8);
    reg |= (readcommand8(cmd_function, index + 1) <<  0);

    return reg;
}


/***************************************************************************************
** Function name:           readcommand32
** Description:             Read a 32 bit data value from an indexed command register
***************************************************************************************/
uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index) {
    uint32_t reg;

    reg  = (readcommand8(cmd_function, index + 0) << 24);
    reg |= (readcommand8(cmd_function, index + 1) << 16);
    reg |= (readcommand8(cmd_function, index + 2) <<  8);
    reg |= (readcommand8(cmd_function, index + 3) <<  0);

    return reg;
}


/***************************************************************************************
** Function name:           read pixel (for SPI Interface II i.e. IM [3:0] = "1101")
** Description:             Read 565 pixel colours from a pixel
***************************************************************************************/
uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0) {

    com_begin_read();

    readAddrWindow(x0, y0, 1, 1); // Sets CS low

    #ifdef TFT_SDA_READ
    begin_SDA_Read();
    #endif

    // Dummy read to throw away don't care value
    tft_Read_8();

    //#if !defined (ILI9488_DRIVER)

    // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
    // as the TFT stores colours as 18 bits
    uint8_t r = tft_Read_8();
    uint8_t g = tft_Read_8();
    uint8_t b = tft_Read_8();
    /*
        #else

        // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse
        // so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left
        uint8_t r = (tft_Read_8()&0x7E)<<1;
        uint8_t g = (tft_Read_8()&0x7E)<<1;
        uint8_t b = (tft_Read_8()&0x7E)<<1;

        #endif
    */
    CS_H;

    #ifdef TFT_SDA_READ
    end_SDA_Read();
    #endif

    com_end_read();

    return color565(r, g, b);
}

/***************************************************************************************
** Function name:           read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
** Description:             Read 565 pixel colours from a defined area
***************************************************************************************/
void TFT_eSPI::readRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* data) {
    if ((x > _width) || (y > _height) || (w == 0) || (h == 0)) {
        return;
    }


    com_begin_read();

    readAddrWindow(x, y, w, h); // Sets CS low

    #ifdef TFT_SDA_READ
    begin_SDA_Read();
    #endif

    // Dummy read to throw away don't care value
    tft_Read_8();

    // Read window pixel 24 bit RGB values
    uint32_t len = w * h;
    while (len--) {

        #if !defined (ILI9488_DRIVER)

        // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
        // as the TFT stores colours as 18 bits
        uint8_t r = tft_Read_8();
        uint8_t g = tft_Read_8();
        uint8_t b = tft_Read_8();

        #else

        // The 6 colour bits are in LS 6 bits of each byte but we do not include the extra clock pulse
        // so we use a trick and mask the middle 6 bits of the byte, then only shift 1 place left
        uint8_t r = (tft_Read_8() & 0x7E) << 1;
        uint8_t g = (tft_Read_8() & 0x7E) << 1;
        uint8_t b = (tft_Read_8() & 0x7E) << 1;

        #endif

        // Swapped colour byte order for compatibility with pushRect()
        *data++ = (r & 0xF8) | (g & 0xE0) >> 5 | (b & 0xF8) << 5 | (g & 0x1C) << 11;
    }

    CS_H;

    #ifdef TFT_SDA_READ
    end_SDA_Read();
    #endif

    com_end_read();
}

/***************************************************************************************
** Function name:           beginSDA
** Description:             Detach SPI from pin to permit software SPI
***************************************************************************************/
#ifdef TFT_SDA_READ
void TFT_eSPI::begin_SDA_Read(void) {

    #ifdef TFT_SPI_OVERLAP
    // Reads in overlap mode not supported
    #else
    _com.end();
    #endif

}
#endif

/***************************************************************************************
** Function name:           endSDA
** Description:             Attach SPI pins after software SPI
***************************************************************************************/
#ifdef TFT_SDA_READ
void TFT_eSPI::end_SDA_Read(void) {

    #ifdef TFT_SPI_OVERLAP
    _com.pins(6, 7, 8, 0);
    #else
    _com.begin();
    #endif
}
#endif

/***************************************************************************************
** Function name:           push rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
** Description:             push 565 pixel colours into a defined area
***************************************************************************************/
void TFT_eSPI::pushRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* data) {
    // Function deprecated, remains for backwards compatibility
    // pushImage() is better as it will crop partly off-screen image blocks
    pushImage(x, y, w, h, data);
}


/***************************************************************************************
** Function name:           pushImage
** Description:             plot 16 bit colour sprite or image onto TFT
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* data) {

    if ((x >= _width) || (y >= _height)) {
        return;
    }

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y = 0;
    }

    if ((x + dw) > _width) {
        dw = _width  - x;
    }
    if ((y + dh) > _height) {
        dh = _height - y;
    }

    if (dw < 1 || dh < 1) {
        return;
    }

    com_begin();
    inTransaction = true;

    setWindow(x, y, x + dw - 1, y + dh - 1);

    data += dx + dy * w;

    while (dh--) {
        pushColors(data, dw, _swapBytes);
        data += w;
    }

    inTransaction = false;
    com_end();
}

/***************************************************************************************
** Function name:           pushImage
** Description:             plot 16 bit sprite or image with 1 colour being transparent
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* data, uint16_t transp) {

    if ((x >= _width) || (y >= _height)) {
        return;
    }

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y = 0;
    }

    if ((x + dw) > _width) {
        dw = _width  - x;
    }
    if ((y + dh) > _height) {
        dh = _height - y;
    }

    if (dw < 1 || dh < 1) {
        return;
    }

    com_begin();
    inTransaction = true;

    data += dx + dy * w;

    int32_t xe = x + dw - 1, ye = y + dh - 1;

    uint16_t  lineBuf[dw];

    if (!_swapBytes) {
        transp = transp >> 8 | transp << 8;
    }

    while (dh--) {
        int32_t len = dw;
        uint16_t* ptr = data;
        int32_t px = x;
        boolean move = true;
        uint16_t np = 0;

        while (len--) {
            if (transp != *ptr) {
                if (move) {
                    move = false;
                    setWindow(px, y, xe, ye);
                }
                lineBuf[np] = *ptr;
                np++;
            } else {
                move = true;
                if (np) {
                    pushColors((uint16_t*)lineBuf, np, _swapBytes);
                    np = 0;
                }
            }
            px++;
            ptr++;
        }
        if (np) {
            pushColors((uint16_t*)lineBuf, np, _swapBytes);
        }

        y++;
        data += w;
    }

    inTransaction = false;
    com_end();
}


/***************************************************************************************
** Function name:           pushImage - for FLASH (PROGMEM) stored images
** Description:             plot 16 bit image
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t* data) {

    if ((x >= _width) || (y >= _height)) {
        return;
    }

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y = 0;
    }

    if ((x + dw) > _width) {
        dw = _width  - x;
    }
    if ((y + dh) > _height) {
        dh = _height - y;
    }

    if (dw < 1 || dh < 1) {
        return;
    }

    com_begin();
    inTransaction = true;

    data += dx + dy * w;

    uint16_t  buffer[64];
    uint16_t* pix_buffer = buffer;

    setWindow(x, y, x + dw - 1, y + dh - 1);

    // Work out the number whole buffers to send
    uint16_t nb = (dw * dh) / 64;

    // Fill and send "nb" buffers to TFT
    for (int32_t i = 0; i < nb; i++) {
        for (int32_t j = 0; j < 64; j++) {
            pix_buffer[j] = pgm_read_word(&data[i * 64 + j]);
        }
        pushColors(pix_buffer, 64, _swapBytes);
    }

    // Work out number of pixels not yet sent
    uint16_t np = (dw * dh) % 64;

    // Send any partial buffer left over
    if (np) {
        for (int32_t i = 0; i < np; i++) {
            pix_buffer[i] = pgm_read_word(&data[nb * 64 + i]);
        }
        pushColors(pix_buffer, np, _swapBytes);
    }

    inTransaction = false;
    com_end();
}


/***************************************************************************************
** Function name:           pushImage - for FLASH (PROGMEM) stored images
** Description:             plot 16 bit image with 1 colour being transparent
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t* data, uint16_t transp) {
    // Partitioned memory FLASH processor
    if ((x >= _width) || (y >= (int32_t)_height)) {
        return;
    }

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y = 0;
    }

    if ((x + dw) > _width) {
        dw = _width  - x;
    }
    if ((y + dh) > _height) {
        dh = _height - y;
    }

    if (dw < 1 || dh < 1) {
        return;
    }

    com_begin();
    inTransaction = true;

    data += dx + dy * w;

    int32_t xe = x + dw - 1, ye = y + dh - 1;

    uint16_t  lineBuf[dw];

    if (!_swapBytes) {
        transp = transp >> 8 | transp << 8;
    }

    while (dh--) {
        int32_t len = dw;
        uint16_t* ptr = (uint16_t*)data;
        int32_t px = x;
        boolean move = true;

        uint16_t np = 0;

        while (len--) {
            uint16_t color = pgm_read_word(ptr);
            if (transp != color) {
                if (move) {
                    move = false;
                    setWindow(px, y, xe, ye);
                }
                lineBuf[np] = color;
                np++;
            } else {
                move = true;
                if (np) {
                    pushColors(lineBuf, np, _swapBytes);
                    np = 0;
                }
            }
            px++;
            ptr++;
        }
        if (np) {
            pushColors(lineBuf, np, _swapBytes);
        }

        y++;
        data += w;
    }

    inTransaction = false;
    com_end();
}


/***************************************************************************************
** Function name:           pushImage
** Description:             plot 8 bit image or sprite using a line buffer
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t* data, bool bpp8) {
    if ((x >= _width) || (y >= (int32_t)_height)) {
        return;
    }

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y = 0;
    }

    if ((x + dw) > _width) {
        dw = _width  - x;
    }
    if ((y + dh) > _height) {
        dh = _height - y;
    }

    if (dw < 1 || dh < 1) {
        return;
    }

    com_begin();
    inTransaction = true;

    setWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR

    // Line buffer makes plotting faster
    uint16_t  lineBuf[dw];

    if (bpp8) {
        uint8_t  blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table

        _lastColor = -1; // Set to illegal value

        // Used to store last shifted colour
        uint8_t msbColor = 0;
        uint8_t lsbColor = 0;

        data += dx + dy * w;
        while (dh--) {
            uint32_t len = dw;
            uint8_t* ptr = data;
            uint8_t* linePtr = (uint8_t*)lineBuf;

            while (len--) {
                uint32_t color = *ptr++;

                // Shifts are slow so check if colour has changed first
                if (color != _lastColor) {
                    //          =====Green=====     ===============Red==============
                    msbColor = (color & 0x1C) >> 2 | (color & 0xC0) >> 3 | (color & 0xE0);
                    //          =====Green=====    =======Blue======
                    lsbColor = (color & 0x1C) << 3 | blue[color & 0x03];
                    _lastColor = color;
                }

                *linePtr++ = msbColor;
                *linePtr++ = lsbColor;
            }

            pushColors(lineBuf, dw, false);

            data += w;
        }
    } else {
        while (dh--) {
            w = (w + 7) & 0xFFF8;

            int32_t len = dw;
            uint8_t* ptr = data;
            uint8_t* linePtr = (uint8_t*)lineBuf;
            uint8_t  bits = 8;
            while (len > 0) {
                if (len < 8) {
                    bits = len;
                }
                uint32_t xp = dx;
                for (uint16_t i = 0; i < bits; i++) {
                    uint8_t col = (ptr[(xp + dy * w) >> 3] << (xp & 0x7)) & 0x80;
                    if (col) {
                        *linePtr++ = bitmap_fg >> 8;
                        *linePtr++ = (uint8_t) bitmap_fg;
                    } else     {
                        *linePtr++ = bitmap_bg >> 8;
                        *linePtr++ = (uint8_t) bitmap_bg;
                    }
                    //if (col) drawPixel((dw-len)+xp,h-dh,bitmap_fg);
                    //else     drawPixel((dw-len)+xp,h-dh,bitmap_bg);
                    xp++;
                }
                ptr++;
                len -= 8;
            }

            pushColors(lineBuf, dw, false);

            dy++;
        }
    }

    inTransaction = false;
    com_end();
}


/***************************************************************************************
** Function name:           pushImage
** Description:             plot 8 or 1 bit image or sprite with a transparent colour
***************************************************************************************/
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t* data, uint8_t transp, bool bpp8) {
    if ((x >= _width) || (y >= _height)) {
        return;
    }

    int32_t dx = 0;
    int32_t dy = 0;
    int32_t dw = w;
    int32_t dh = h;

    if (x < 0) {
        dw += x;
        dx = -x;
        x = 0;
    }
    if (y < 0) {
        dh += y;
        dy = -y;
        y = 0;
    }

    if ((x + dw) > _width) {
        dw = _width  - x;
    }
    if ((y + dh) > _height) {
        dh = _height - y;
    }

    if (dw < 1 || dh < 1) {
        return;
    }

    com_begin();
    inTransaction = true;

    int32_t xe = x + dw - 1, ye = y + dh - 1;

    // Line buffer makes plotting faster
    uint16_t  lineBuf[dw];

    if (bpp8) {
        data += dx + dy * w;

        uint8_t  blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table

        _lastColor = -1; // Set to illegal value

        // Used to store last shifted colour
        uint8_t msbColor = 0;
        uint8_t lsbColor = 0;

        //int32_t spx = x, spy = y;

        while (dh--) {
            int32_t len = dw;
            uint8_t* ptr = data;
            uint8_t* linePtr = (uint8_t*)lineBuf;

            int32_t px = x;
            boolean move = true;
            uint16_t np = 0;

            while (len--) {
                if (transp != *ptr) {
                    if (move) {
                        move = false;
                        setWindow(px, y, xe, ye);
                    }
                    uint8_t color = *ptr;

                    // Shifts are slow so check if colour has changed first
                    if (color != _lastColor) {
                        //          =====Green=====     ===============Red==============
                        msbColor = (color & 0x1C) >> 2 | (color & 0xC0) >> 3 | (color & 0xE0);
                        //          =====Green=====    =======Blue======
                        lsbColor = (color & 0x1C) << 3 | blue[color & 0x03];
                        _lastColor = color;
                    }
                    *linePtr++ = msbColor;
                    *linePtr++ = lsbColor;
                    np++;
                } else {
                    move = true;
                    if (np) {
                        pushColors(lineBuf, np, false);
                        linePtr = (uint8_t*)lineBuf;
                        np = 0;
                    }
                }
                px++;
                ptr++;
            }

            if (np) {
                pushColors(lineBuf, np, false);
            }

            y++;
            data += w;
        }
    } else {
        w = (w + 7) & 0xFFF8;
        while (dh--) {
            int32_t px = x;
            boolean move = true;
            uint16_t np = 0;
            int32_t len = dw;
            uint8_t* ptr = data;
            uint8_t  bits = 8;
            while (len > 0) {
                if (len < 8) {
                    bits = len;
                }
                uint32_t xp = dx;
                uint32_t yp = (dy * w) >> 3;
                for (uint16_t i = 0; i < bits; i++) {
                    //uint8_t col = (ptr[(xp + dy * w)>>3] << (xp & 0x7)) & 0x80;
                    if ((ptr[(xp >> 3) + yp] << (xp & 0x7)) & 0x80) {
                        if (move) {
                            move = false;
                            setWindow(px, y, xe, ye);
                        }
                        np++;
                    } else {
                        if (np) {
                            pushColor(bitmap_fg, np);
                            np = 0;
                            move = true;
                        }
                    }
                    px++;
                    xp++;
                }
                ptr++;
                len -= 8;
            }
            if (np) {
                pushColor(bitmap_fg, np);
            }
            y++;
            dy++;
        }
    }

    inTransaction = false;
    com_end();
}


/***************************************************************************************
** Function name:           setSwapBytes
** Description:             Used by 16 bit pushImage() to swap byte order in colours
***************************************************************************************/
void TFT_eSPI::setSwapBytes(bool swap) {
    _swapBytes = swap;
}


/***************************************************************************************
** Function name:           getSwapBytes
** Description:             Return the swap byte order for colours
***************************************************************************************/
bool TFT_eSPI::getSwapBytes(void) {
    return _swapBytes;
}

/***************************************************************************************
** Function name:           read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
** Description:             Read RGB pixel colours from a defined area
***************************************************************************************/
// If w and h are 1, then 1 pixel is read, *data array size must be 3 bytes per pixel
void  TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t* data) {
    com_begin_read();

    readAddrWindow(x0, y0, w, h); // Sets CS low

    #ifdef TFT_SDA_READ
    begin_SDA_Read();
    #endif

    // Dummy read to throw away don't care value
    tft_Read_8();

    // Read window pixel 24 bit RGB values, buffer must be set in sketch to 3 * w * h
    uint32_t len = w * h;
    while (len--) {

        #if !defined (ILI9488_DRIVER)

        // Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
        // as the TFT stores colours as 18 bits
        *data++ = tft_Read_8();
        *data++ = tft_Read_8();
        *data++ = tft_Read_8();

        #else

        // The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse
        // so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left
        *data++ = (tft_Read_8() & 0x7E) << 1;
        *data++ = (tft_Read_8() & 0x7E) << 1;
        *data++ = (tft_Read_8() & 0x7E) << 1;

        #endif

    }

    CS_H;

    #ifdef TFT_SDA_READ
    end_SDA_Read();
    #endif

    com_end_read();

}


/***************************************************************************************
** Function name:           drawCircle
** Description:             Draw a circle outline
***************************************************************************************/
// Optimised midpoint circle algorithm
void TFT_eSPI::drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) {
    int32_t  x  = 0;
    int32_t  dx = 1;
    int32_t  dy = r + r;
    int32_t  p  = -(r >> 1);

    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    // These are ordered to minimise coordinate changes in x or y
    // drawPixel can then send fewer bounding box commands
    drawPixel(x0 + r, y0, color);
    drawPixel(x0 - r, y0, color);
    drawPixel(x0, y0 - r, color);
    drawPixel(x0, y0 + r, color);

    while (x < r) {

        if (p >= 0) {
            dy -= 2;
            p -= dy;
            r--;
        }

        dx += 2;
        p += dx;

        x++;

        // These are ordered to minimise coordinate changes in x or y
        // drawPixel can then send fewer bounding box commands
        drawPixel(x0 + x, y0 + r, color);
        drawPixel(x0 - x, y0 + r, color);
        drawPixel(x0 - x, y0 - r, color);
        drawPixel(x0 + x, y0 - r, color);

        drawPixel(x0 + r, y0 + x, color);
        drawPixel(x0 - r, y0 + x, color);
        drawPixel(x0 - r, y0 - x, color);
        drawPixel(x0 + r, y0 - x, color);
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           drawCircleHelper
** Description:             Support function for circle drawing
***************************************************************************************/
void TFT_eSPI::drawCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, uint32_t color) {
    int32_t f     = 1 - r;
    int32_t ddF_x = 1;
    int32_t ddF_y = -2 * r;
    int32_t x     = 0;

    while (x < r) {
        if (f >= 0) {
            r--;
            ddF_y += 2;
            f     += ddF_y;
        }
        x++;
        ddF_x += 2;
        f     += ddF_x;
        if (cornername & 0x4) {
            drawPixel(x0 + x, y0 + r, color);
            drawPixel(x0 + r, y0 + x, color);
        }
        if (cornername & 0x2) {
            drawPixel(x0 + x, y0 - r, color);
            drawPixel(x0 + r, y0 - x, color);
        }
        if (cornername & 0x8) {
            drawPixel(x0 - r, y0 + x, color);
            drawPixel(x0 - x, y0 + r, color);
        }
        if (cornername & 0x1) {
            drawPixel(x0 - r, y0 - x, color);
            drawPixel(x0 - x, y0 - r, color);
        }
    }
}


/***************************************************************************************
** Function name:           fillCircle
** Description:             draw a filled circle
***************************************************************************************/
// Optimised midpoint circle algorithm, changed to horizontal lines (faster in sprites)
void TFT_eSPI::fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color) {
    int32_t  x  = 0;
    int32_t  dx = 1;
    int32_t  dy = r + r;
    int32_t  p  = -(r >> 1);

    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    drawFastHLine(x0 - r, y0, dy + 1, color);

    while (x < r) {

        if (p >= 0) {
            dy -= 2;
            p -= dy;
            r--;
        }

        dx += 2;
        p += dx;

        x++;

        drawFastHLine(x0 - r, y0 + x, 2 * r + 1, color);
        drawFastHLine(x0 - r, y0 - x, 2 * r + 1, color);
        drawFastHLine(x0 - x, y0 + r, 2 * x + 1, color);
        drawFastHLine(x0 - x, y0 - r, 2 * x + 1, color);

    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           fillCircleHelper
** Description:             Support function for filled circle drawing
***************************************************************************************/
// Used to support drawing roundrects, changed to horizontal lines (faster in sprites)
void TFT_eSPI::fillCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, int32_t delta, uint32_t color) {
    int32_t f     = 1 - r;
    int32_t ddF_x = 1;
    int32_t ddF_y = -r - r;
    int32_t y     = 0;

    delta++;
    while (y < r) {
        if (f >= 0) {
            r--;
            ddF_y += 2;
            f     += ddF_y;
        }
        y++;
        //x++;
        ddF_x += 2;
        f     += ddF_x;

        if (cornername & 0x1) {
            drawFastHLine(x0 - r, y0 + y, r + r + delta, color);
            drawFastHLine(x0 - y, y0 + r, y + y + delta, color);
        }
        if (cornername & 0x2) {
            drawFastHLine(x0 - r, y0 - y, r + r + delta, color); // 11995, 1090
            drawFastHLine(x0 - y, y0 - r, y + y + delta, color);
        }
    }
}


/***************************************************************************************
** Function name:           drawEllipse
** Description:             Draw a ellipse outline
***************************************************************************************/
void TFT_eSPI::drawEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color) {
    if (rx < 2) {
        return;
    }
    if (ry < 2) {
        return;
    }
    int32_t x, y;
    int32_t rx2 = rx * rx;
    int32_t ry2 = ry * ry;
    int32_t fx2 = 4 * rx2;
    int32_t fy2 = 4 * ry2;
    int32_t s;

    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) {
        // These are ordered to minimise coordinate changes in x or y
        // drawPixel can then send fewer bounding box commands
        drawPixel(x0 + x, y0 + y, color);
        drawPixel(x0 - x, y0 + y, color);
        drawPixel(x0 - x, y0 - y, color);
        drawPixel(x0 + x, y0 - y, color);
        if (s >= 0) {
            s += fx2 * (1 - y);
            y--;
        }
        s += ry2 * ((4 * x) + 6);
    }

    for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) {
        // These are ordered to minimise coordinate changes in x or y
        // drawPixel can then send fewer bounding box commands
        drawPixel(x0 + x, y0 + y, color);
        drawPixel(x0 - x, y0 + y, color);
        drawPixel(x0 - x, y0 - y, color);
        drawPixel(x0 + x, y0 - y, color);
        if (s >= 0) {
            s += fy2 * (1 - x);
            x--;
        }
        s += rx2 * ((4 * y) + 6);
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           fillEllipse
** Description:             draw a filled ellipse
***************************************************************************************/
void TFT_eSPI::fillEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color) {
    if (rx < 2) {
        return;
    }
    if (ry < 2) {
        return;
    }
    int32_t x, y;
    int32_t rx2 = rx * rx;
    int32_t ry2 = ry * ry;
    int32_t fx2 = 4 * rx2;
    int32_t fy2 = 4 * ry2;
    int32_t s;

    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) {
        drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
        drawFastHLine(x0 - x, y0 + y, x + x + 1, color);

        if (s >= 0) {
            s += fx2 * (1 - y);
            y--;
        }
        s += ry2 * ((4 * x) + 6);
    }

    for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) {
        drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
        drawFastHLine(x0 - x, y0 + y, x + x + 1, color);

        if (s >= 0) {
            s += fy2 * (1 - x);
            x--;
        }
        s += rx2 * ((4 * y) + 6);
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           fillScreen
** Description:             Clear the screen to defined colour
***************************************************************************************/
void TFT_eSPI::fillScreen(uint32_t color) {
    fillRect(0, 0, _width, _height, color);
}


/***************************************************************************************
** Function name:           drawRect
** Description:             Draw a rectangle outline
***************************************************************************************/
// Draw a rectangle
void TFT_eSPI::drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    drawFastHLine(x, y, w, color);
    drawFastHLine(x, y + h - 1, w, color);
    // Avoid drawing corner pixels twice
    drawFastVLine(x, y + 1, h - 2, color);
    drawFastVLine(x + w - 1, y + 1, h - 2, color);

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           drawRoundRect
** Description:             Draw a rounded corner rectangle outline
***************************************************************************************/
// Draw a rounded rectangle
void TFT_eSPI::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    // smarter version
    drawFastHLine(x + r, y, w - r - r, color);       // Top
    drawFastHLine(x + r, y + h - 1, w - r - r, color);   // Bottom
    drawFastVLine(x, y + r, h - r - r, color);       // Left
    drawFastVLine(x + w - 1, y + r, h - r - r, color);   // Right
    // draw four corners
    drawCircleHelper(x + r, y + r, r, 1, color);
    drawCircleHelper(x + w - r - 1, y + r, r, 2, color);
    drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
    drawCircleHelper(x + r, y + h - r - 1, r, 8, color);

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           fillRoundRect
** Description:             Draw a rounded corner filled rectangle
***************************************************************************************/
// Fill a rounded rectangle, changed to horizontal lines (faster in sprites)
void TFT_eSPI::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    // smarter version
    fillRect(x, y + r, w, h - r - r, color);

    // draw four corners
    fillCircleHelper(x + r, y + h - r - 1, r, 1, w - r - r - 1, color);
    fillCircleHelper(x + r, y + r, r, 2, w - r - r - 1, color);

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           drawTriangle
** Description:             Draw a triangle outline using 3 arbitrary points
***************************************************************************************/
// Draw a triangle
void TFT_eSPI::drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    drawLine(x0, y0, x1, y1, color);
    drawLine(x1, y1, x2, y2, color);
    drawLine(x2, y2, x0, y0, color);

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           fillTriangle
** Description:             Draw a filled triangle using 3 arbitrary points
***************************************************************************************/
// Fill a triangle - original Adafruit function works well and code footprint is small
void TFT_eSPI::fillTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) {
    int32_t a, b, y, last;

    // Sort coordinates by Y order (y2 >= y1 >= y0)
    if (y0 > y1) {
        swap_coord(y0, y1); swap_coord(x0, x1);
    }
    if (y1 > y2) {
        swap_coord(y2, y1); swap_coord(x2, x1);
    }
    if (y0 > y1) {
        swap_coord(y0, y1); swap_coord(x0, x1);
    }

    if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
        a = b = x0;
        if (x1 < a) {
            a = x1;
        } else if (x1 > b) {
            b = x1;
        }
        if (x2 < a) {
            a = x2;
        } else if (x2 > b) {
            b = x2;
        }
        drawFastHLine(a, y0, b - a + 1, color);
        return;
    }

    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    int32_t
    dx01 = x1 - x0,
    dy01 = y1 - y0,
    dx02 = x2 - x0,
    dy02 = y2 - y0,
    dx12 = x2 - x1,
    dy12 = y2 - y1,
    sa   = 0,
    sb   = 0;

    // For upper part of triangle, find scanline crossings for segments
    // 0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
    // is included here (and second loop will be skipped, avoiding a /0
    // error there), otherwise scanline y1 is skipped here and handled
    // in the second loop...which also avoids a /0 error here if y0=y1
    // (flat-topped triangle).
    if (y1 == y2) {
        last = y1;    // Include y1 scanline
    } else {
        last = y1 - 1;    // Skip it
    }

    for (y = y0; y <= last; y++) {
        a   = x0 + sa / dy01;
        b   = x0 + sb / dy02;
        sa += dx01;
        sb += dx02;

        if (a > b) {
            swap_coord(a, b);
        }
        drawFastHLine(a, y, b - a + 1, color);
    }

    // For lower part of triangle, find scanline crossings for segments
    // 0-2 and 1-2.  This loop is skipped if y1=y2.
    sa = dx12 * (y - y1);
    sb = dx02 * (y - y0);
    for (; y <= y2; y++) {
        a   = x1 + sa / dy12;
        b   = x0 + sb / dy02;
        sa += dx12;
        sb += dx02;

        if (a > b) {
            swap_coord(a, b);
        }
        drawFastHLine(a, y, b - a + 1, color);
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           drawBitmap
** Description:             Draw an image stored in an array on the TFT
***************************************************************************************/
void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t* bitmap, int16_t w, int16_t h, uint16_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    int32_t i, j, byteWidth = (w + 7) / 8;

    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
                drawPixel(x + i, y + j, color);
            }
        }
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           drawXBitmap
** Description:             Draw an image stored in an XBM array onto the TFT
***************************************************************************************/
void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t* bitmap, int16_t w, int16_t h, uint16_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    int32_t i, j, byteWidth = (w + 7) / 8;

    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7))) {
                drawPixel(x + i, y + j, color);
            }
        }
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           drawXBitmap
** Description:             Draw an XBM image with foreground and background colors
***************************************************************************************/
void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t* bitmap, int16_t w, int16_t h, uint16_t color,
                           uint16_t bgcolor) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;

    int32_t i, j, byteWidth = (w + 7) / 8;

    for (j = 0; j < h; j++) {
        for (i = 0; i < w; i++) {
            if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7))) {
                drawPixel(x + i, y + j,   color);
            } else {
                drawPixel(x + i, y + j, bgcolor);
            }
        }
    }

    inTransaction = false;
    com_end();              // Does nothing if Sprite class uses this function
}


/***************************************************************************************
** Function name:           setCursor
** Description:             Set the text cursor x,y position
***************************************************************************************/
void TFT_eSPI::setCursor(int16_t x, int16_t y) {
    cursor_x = x;
    cursor_y = y;
}


/***************************************************************************************
** Function name:           setCursor
** Description:             Set the text cursor x,y position and font
***************************************************************************************/
void TFT_eSPI::setCursor(int16_t x, int16_t y, uint8_t font) {
    textfont = font;
    cursor_x = x;
    cursor_y = y;
}


/***************************************************************************************
** Function name:           getCursorX
** Description:             Get the text cursor x position
***************************************************************************************/
int16_t TFT_eSPI::getCursorX(void) {
    return cursor_x;
}

/***************************************************************************************
** Function name:           getCursorY
** Description:             Get the text cursor y position
***************************************************************************************/
int16_t TFT_eSPI::getCursorY(void) {
    return cursor_y;
}


/***************************************************************************************
** Function name:           setTextSize
** Description:             Set the text size multiplier
***************************************************************************************/
void TFT_eSPI::setTextSize(uint8_t s) {
    if (s > 7) {
        s = 7;    // Limit the maximum size multiplier so byte variables can be used for rendering
    }
    textsize = (s > 0) ? s : 1; // Don't allow font size 0
}


/***************************************************************************************
** Function name:           setTextColor
** Description:             Set the font foreground colour (background is transparent)
***************************************************************************************/
void TFT_eSPI::setTextColor(uint16_t c) {
    // For 'transparent' background, we'll set the bg
    // to the same as fg instead of using a flag
    textcolor = textbgcolor = c;
}


/***************************************************************************************
** Function name:           setTextColor
** Description:             Set the font foreground and background colour
***************************************************************************************/
void TFT_eSPI::setTextColor(uint16_t c, uint16_t b) {
    textcolor   = c;
    textbgcolor = b;
}


/***************************************************************************************
** Function name:           setPivot
** Description:             Set the pivot point on the TFT
*************************************************************************************x*/
void TFT_eSPI::setPivot(int16_t x, int16_t y) {
    _xpivot = x;
    _ypivot = y;
}


/***************************************************************************************
** Function name:           getPivotX
** Description:             Get the x pivot position
***************************************************************************************/
int16_t TFT_eSPI::getPivotX(void) {
    return _xpivot;
}


/***************************************************************************************
** Function name:           getPivotY
** Description:             Get the y pivot position
***************************************************************************************/
int16_t TFT_eSPI::getPivotY(void) {
    return _ypivot;
}


/***************************************************************************************
** Function name:           setBitmapColor
** Description:             Set the foreground foreground and background colour
***************************************************************************************/
void TFT_eSPI::setBitmapColor(uint16_t c, uint16_t b) {
    if (c == b) {
        b = ~c;
    }
    bitmap_fg = c;
    bitmap_bg = b;
}


/***************************************************************************************
** Function name:           setTextWrap
** Description:             Define if text should wrap at end of line
***************************************************************************************/
void TFT_eSPI::setTextWrap(boolean wrapX, boolean wrapY) {
    textwrapX = wrapX;
    textwrapY = wrapY;
}


/***************************************************************************************
** Function name:           setTextDatum
** Description:             Set the text position reference datum
***************************************************************************************/
void TFT_eSPI::setTextDatum(uint8_t d) {
    textdatum = d;
}


/***************************************************************************************
** Function name:           setTextPadding
** Description:             Define padding width (aids erasing old text and numbers)
***************************************************************************************/
void TFT_eSPI::setTextPadding(uint16_t x_width) {
    padX = x_width;
}


/***************************************************************************************
** Function name:           getRotation
** Description:             Return the rotation value (as used by setRotation())
***************************************************************************************/
uint8_t TFT_eSPI::getRotation(void) {
    return rotation;
}

/***************************************************************************************
** Function name:           getTextDatum
** Description:             Return the text datum value (as used by setTextDatum())
***************************************************************************************/
uint8_t TFT_eSPI::getTextDatum(void) {
    return textdatum;
}


/***************************************************************************************
** Function name:           width
** Description:             Return the pixel width of display (per current rotation)
***************************************************************************************/
// Return the size of the display (per current rotation)
int16_t TFT_eSPI::width(void) {
    return _width;
}


/***************************************************************************************
** Function name:           height
** Description:             Return the pixel height of display (per current rotation)
***************************************************************************************/
int16_t TFT_eSPI::height(void) {
    return _height;
}


/***************************************************************************************
** Function name:           textWidth
** Description:             Return the width in pixels of a string in a given font
***************************************************************************************/
int16_t TFT_eSPI::textWidth(const String& string) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return textWidth(buffer, textfont);
}

int16_t TFT_eSPI::textWidth(const String& string, uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return textWidth(buffer, font);
}

int16_t TFT_eSPI::textWidth(const char* string) {
    return textWidth(string, textfont);
}

int16_t TFT_eSPI::textWidth(const char* string, uint8_t font) {
    int32_t str_width = 0;
    uint16_t uniCode  = 0;

    #ifdef SMOOTH_FONT
    if (fontLoaded) {
        while (*string) {
            uniCode = decodeUTF8(*string++);
            if (uniCode) {
                if (uniCode == 0x20) {
                    str_width += gFont.spaceWidth;
                } else {
                    uint16_t gNum = 0;
                    bool found = getUnicodeIndex(uniCode, &gNum);
                    if (found) {
                        if (str_width == 0 && gdX[gNum] < 0) {
                            str_width -= gdX[gNum];
                        }
                        if (*string || isDigits) {
                            str_width += gxAdvance[gNum];
                        } else {
                            str_width += (gdX[gNum] + gWidth[gNum]);
                        }
                    } else {
                        str_width += gFont.spaceWidth + 1;
                    }
                }
            }
        }
        isDigits = false;
        return str_width;
    }
    #endif

    if (font > 1 && font < 9) {
        char* widthtable = (char*)pgm_read_dword(&(fontdata[font].widthtbl)) - 32;     //subtract the 32 outside the loop

        while (*string) {
            uniCode = *(string++);
            if (uniCode > 31 && uniCode < 128) {
                str_width += pgm_read_byte(widthtable + uniCode);    // Normally we need to subtract 32 from uniCode
            } else {
                str_width += pgm_read_byte(widthtable + 32);    // Set illegal character = space width
            }
        }

    } else {

        #ifdef LOAD_GFXFF
        if (gfxFont) { // New font
            while (*string) {
                uniCode = decodeUTF8(*string++);
                if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last))) {
                    uniCode -= pgm_read_word(&gfxFont->first);
                    GFXglyph* glyph  = &(((GFXglyph*)pgm_read_dword(&gfxFont->glyph))[uniCode]);
                    // If this is not the  last character or is a digit then use xAdvance
                    if (*string  || isDigits) {
                        str_width += pgm_read_byte(&glyph->xAdvance);
                    }
                    // Else use the offset plus width since this can be bigger than xAdvance
                    else {
                        str_width += ((int8_t)pgm_read_byte(&glyph->xOffset) + pgm_read_byte(&glyph->width));
                    }
                }
            }
        } else
        #endif
        {
            #ifdef LOAD_GLCD
            while (*string++) {
                str_width += 6;
            }
            #endif
        }
    }
    isDigits = false;
    return str_width * textsize;
}


/***************************************************************************************
** Function name:           fontsLoaded
** Description:             return an encoded 16 bit value showing the fonts loaded
***************************************************************************************/
// Returns a value showing which fonts are loaded (bit N set =  Font N loaded)

uint16_t TFT_eSPI::fontsLoaded(void) {
    return fontsloaded;
}


/***************************************************************************************
** Function name:           fontHeight
** Description:             return the height of a font (yAdvance for free fonts)
***************************************************************************************/
int16_t TFT_eSPI::fontHeight(int16_t font) {
    #ifdef SMOOTH_FONT
    if (fontLoaded) {
        return gFont.yAdvance;
    }
    #endif

    #ifdef LOAD_GFXFF
    if (font == 1) {
        if (gfxFont) { // New font
            return pgm_read_byte(&gfxFont->yAdvance) * textsize;
        }
    }
    #endif
    return pgm_read_byte(&fontdata[font].height) * textsize;
}

int16_t TFT_eSPI::fontHeight(void) {
    return fontHeight(textfont);
}

/***************************************************************************************
** Function name:           drawChar
** Description:             draw a single character in the Adafruit GLCD font
***************************************************************************************/
void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size) {
    if ((x >= _width)            || // Clip right
            (y >= _height)           || // Clip bottom
            ((x + 6 * size - 1) < 0) || // Clip left
            ((y + 8 * size - 1) < 0)) { // Clip top
        return;
    }

    if (c < 32) {
        return;
    }
    #ifdef LOAD_GLCD
    //>>>>>>>>>>>>>>>>>>
    #ifdef LOAD_GFXFF
    if (!gfxFont) { // 'Classic' built-in font
    #endif
        //>>>>>>>>>>>>>>>>>>

        boolean fillbg = (bg != color);

        if ((size == 1) && fillbg) {
            uint8_t column[6];
            uint8_t mask = 0x1;
            com_begin();

            setWindow(x, y, x + 5, y + 8);

            for (int8_t i = 0; i < 5; i++) {
                column[i] = pgm_read_byte(font + (c * 5) + i);
            }
            column[5] = 0;

            for (int8_t j = 0; j < 8; j++) {
                for (int8_t k = 0; k < 5; k++) {
                    if (column[k] & mask) {
                        tft_Write_16(color);
                    } else {
                        tft_Write_16(bg);
                    }
                }
                mask <<= 1;
                tft_Write_16(bg);
            }
            com_end();
        } else {
            //com_begin();          // Sprite class can use this function, avoiding com_begin()
            inTransaction = true;
            for (int8_t i = 0; i < 6; i++) {
                uint8_t line;
                if (i == 5) {
                    line = 0x0;
                } else {
                    line = pgm_read_byte(font + (c * 5) + i);
                }

                if (size == 1) { // default size
                    for (int8_t j = 0; j < 8; j++) {
                        if (line & 0x1) {
                            drawPixel(x + i, y + j, color);
                        }
                        line >>= 1;
                    }
                } else { // big size
                    for (int8_t j = 0; j < 8; j++) {
                        if (line & 0x1) {
                            fillRect(x + (i * size), y + (j * size), size, size, color);
                        } else if (fillbg) {
                            fillRect(x + i * size, y + j * size, size, size, bg);
                        }
                        line >>= 1;
                    }
                }
            }
            inTransaction = false;
            com_end();              // Does nothing if Sprite class uses this function
        }

        //>>>>>>>>>>>>>>>>>>>>>>>>>>>
        #ifdef LOAD_GFXFF
    } else { // Custom font
        #endif
        //>>>>>>>>>>>>>>>>>>>>>>>>>>>
    #endif // LOAD_GLCD

        #ifdef LOAD_GFXFF
        // Filter out bad characters not present in font
        if ((c >= pgm_read_word(&gfxFont->first)) && (c <= pgm_read_word(&gfxFont->last))) {
            //com_begin();          // Sprite class can use this function, avoiding com_begin()
            inTransaction = true;
            //>>>>>>>>>>>>>>>>>>>>>>>>>>>

            c -= pgm_read_word(&gfxFont->first);
            GFXglyph* glyph  = &(((GFXglyph*)pgm_read_dword(&gfxFont->glyph))[c]);
            uint8_t*  bitmap = (uint8_t*)pgm_read_dword(&gfxFont->bitmap);

            uint32_t bo = pgm_read_word(&glyph->bitmapOffset);
            uint8_t  w  = pgm_read_byte(&glyph->width),
                     h  = pgm_read_byte(&glyph->height);
            //xa = pgm_read_byte(&glyph->xAdvance);
            int8_t   xo = pgm_read_byte(&glyph->xOffset),
                     yo = pgm_read_byte(&glyph->yOffset);
            uint8_t  xx, yy, bits = 0, bit = 0;
            int16_t  xo16 = 0, yo16 = 0;

            if (size > 1) {
                xo16 = xo;
                yo16 = yo;
            }

            // Here we have 3 versions of the same function just for evaluation purposes
            // Comment out the next two #defines to revert to the slower Adafruit implementation

            // If FAST_LINE is defined then the free fonts are rendered using horizontal lines
            // this makes rendering fonts 2-5 times faster. Particularly good for large fonts.
            // This is an elegant solution since it still uses generic functions present in the
            // stock library.

            // If FAST_SHIFT is defined then a slightly faster (at least for AVR processors)
            // shifting bit mask is used

            // Free fonts don't look good when the size multiplier is >1 so we could remove
            // code if this is not wanted and speed things up

#define FAST_HLINE
#define FAST_SHIFT
            //FIXED_SIZE is an option in User_Setup.h that only works with FAST_LINE enabled

            #ifdef FIXED_SIZE
            x += xo; // Save 88 bytes of FLASH
            y += yo;
            #endif

            #ifdef FAST_HLINE

            #ifdef FAST_SHIFT
            uint16_t hpc = 0; // Horizontal foreground pixel count
            for (yy = 0; yy < h; yy++) {
                for (xx = 0; xx < w; xx++) {
                    if (bit == 0) {
                        bits = pgm_read_byte(&bitmap[bo++]);
                        bit  = 0x80;
                    }
                    if (bits & bit) {
                        hpc++;
                    } else {
                        if (hpc) {
                            #ifndef FIXED_SIZE
                            if (size == 1) {
                                drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
                            } else {
                                fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
                            }
                            #else
                            drawFastHLine(x + xx - hpc, y + yy, hpc, color);
                            #endif
                            hpc = 0;
                        }
                    }
                    bit >>= 1;
                }
                // Draw pixels for this line as we are about to increment yy
                if (hpc) {
                    #ifndef FIXED_SIZE
                    if (size == 1) {
                        drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
                    } else {
                        fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
                    }
                    #else
                    drawFastHLine(x + xx - hpc, y + yy, hpc, color);
                    #endif
                    hpc = 0;
                }
            }
            #else
            uint16_t hpc = 0; // Horizontal foreground pixel count
            for (yy = 0; yy < h; yy++) {
                for (xx = 0; xx < w; xx++) {
                    if (!(bit++ & 7)) {
                        bits = pgm_read_byte(&bitmap[bo++]);
                    }
                    if (bits & 0x80) {
                        hpc++;
                    } else {
                        if (hpc) {
                            if (size == 1) {
                                drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
                            } else {
                                fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
                            }
                            hpc = 0;
                        }
                    }
                    bits <<= 1;
                }
                // Draw pixels for this line as we are about to increment yy
                if (hpc) {
                    if (size == 1) {
                        drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
                    } else {
                        fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
                    }
                    hpc = 0;
                }
            }
            #endif

            #else
            for (yy = 0; yy < h; yy++) {
                for (xx = 0; xx < w; xx++) {
                    if (!(bit++ & 7)) {
                        bits = pgm_read_byte(&bitmap[bo++]);
                    }
                    if (bits & 0x80) {
                        if (size == 1) {
                            drawPixel(x + xo + xx, y + yo + yy, color);
                        } else {
                            fillRect(x + (xo16 + xx)*size, y + (yo16 + yy)*size, size, size, color);
                        }
                    }
                    bits <<= 1;
                }
            }
            #endif
            inTransaction = false;
            com_end();              // Does nothing if Sprite class uses this function
        }
        #endif


        #ifdef LOAD_GLCD
        #ifdef LOAD_GFXFF
    } // End classic vs custom font
        #endif
        #endif

}


/***************************************************************************************
** Function name:           setAddrWindow
** Description:             define an area to receive a stream of pixels
***************************************************************************************/
// Chip select is high at the end of this function
void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t w, int32_t h) {
    com_begin();

    setWindow(x0, y0, x0 + w - 1, y0 + h - 1);

    com_end();
}


/***************************************************************************************
** Function name:           setWindow
** Description:             define an area to receive a stream of pixels
***************************************************************************************/
// Chip select stays low, call com_begin first. Use setAddrWindow() from sketches
void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
    //com_begin(); // Must be called before setWimdow

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

    #ifdef CGRAM_OFFSET
    x0 += colstart;
    x1 += colstart;
    y0 += rowstart;
    y1 += rowstart;
    #endif

    DC_C;

    writecommand(TFT_CASET);

    DC_D;

    #if defined (RPI_ILI9486_DRIVER)
    uint8_t xb[] = { 0, (uint8_t)(x0 >> 8), 0, (uint8_t)(x0 >> 0), 0, (uint8_t)(x1 >> 8), 0, (uint8_t)(x1 >> 0), };
    _com.transfer(&xb[0], 8);
    #else
    tft_Write_32(SPI_32(x0, x1));
    #endif

    DC_C;

    // Row addr set
    writecommand(TFT_PASET);

    DC_D;

    #if defined (RPI_ILI9486_DRIVER)
    uint8_t yb[] = { 0, (uint8_t)(y0 >> 8), 0, (uint8_t)(y0 >> 0), 0, (uint8_t)(y1 >> 8), 0, (uint8_t)(y1 >> 0), };
    _com.transfer(&yb[0], 8);
    #else
    tft_Write_32(SPI_32(y0, y1));
    #endif

    DC_C;

    // write to RAM
    writecommand(TFT_RAMWR);

    DC_D;

    //com_end();
}


/***************************************************************************************
** Function name:           readAddrWindow
** Description:             define an area to read a stream of pixels
***************************************************************************************/

void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h) {

    int32_t xe = xs + w - 1;
    int32_t ye = ys + h - 1;

    addr_col = 0xFFFF;
    addr_row = 0xFFFF;

    #ifdef CGRAM_OFFSET
    xs += colstart;
    xe += colstart;
    ys += rowstart;
    ye += rowstart;
    #endif

    // Column addr set
    DC_C;

    writecommand(TFT_CASET);

    DC_D;

    tft_Write_32(SPI_32(xs, xe));

    // Row addr set
    DC_C;

    writecommand(TFT_PASET);

    DC_D;

    tft_Write_32(SPI_32(ys, ye));

    DC_C;

    writecommand(TFT_RAMRD); // Read CGRAM command

    DC_D;

}
/***************************************************************************************
** Function name:           drawPixel
** Description:             push a single pixel at an arbitrary position
***************************************************************************************/
void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) {
    // Range checking
    if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) {
        return;
    }

    com_begin();

    #ifdef CGRAM_OFFSET
    x += colstart;
    y += rowstart;
    #endif

    DC_C;

    // No need to send x if it has not changed (speeds things up)
    if (addr_col != x) {

        writecommand(TFT_CASET);

        DC_D;

        #if defined (RPI_ILI9486_DRIVER)
        uint8_t xb[] = { 0, (uint8_t)(x >> 8), 0, (uint8_t)(x >> 0), 0, (uint8_t)(x >> 8), 0, (uint8_t)(x >> 0), };
        _com.transfer(&xb[0], 8);
        #else
        tft_Write_32(SPI_32(x, x));
        #endif

        DC_C;

        addr_col = x;
    }

    // No need to send y if it has not changed (speeds things up)
    if (addr_row != y) {

        writecommand(TFT_PASET);

        DC_D;

        #if defined (RPI_ILI9486_DRIVER)
        uint8_t yb[] = { 0, (uint8_t)(y >> 8), 0, (uint8_t)(y >> 0), 0, (uint8_t)(y >> 8), 0, (uint8_t)(y >> 0), };
        _com.transfer(&xb[0], 8);
        #else
        tft_Write_32(SPI_32(y, y));
        #endif

        DC_C;

        addr_row = y;
    }


    writecommand(TFT_RAMWR);

    DC_D;

    tft_Write_16(color);

    com_end();
}

/***************************************************************************************
** Function name:           pushColor
** Description:             push a single pixel
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color) {
    com_begin();

    tft_Write_16(color);

    com_end();
}


/***************************************************************************************
** Function name:           pushColor
** Description:             push a single colour to "len" pixels
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color, uint32_t len) {
    com_begin();

    #ifdef RPI_WRITE_STROBE
    uint8_t colorBin[] = { (uint8_t)(color >> 8), (uint8_t) color };
    if (len) {
        _com.writePattern(&colorBin[0], 2, 1);
    } len--;
    while (len--) {
        WR_L;
        WR_H;
    }
    #else
    writeBlock(color, len);
    #endif

    com_end();
}

/***************************************************************************************
** Function name:           startWrite
** Description:             begin transaction with CS low, MUST later call endWrite
***************************************************************************************/
void TFT_eSPI::startWrite(void) {
    com_begin();
    inTransaction = true;
}

/***************************************************************************************
** Function name:           endWrite
** Description:             end transaction with CS high
***************************************************************************************/
void TFT_eSPI::endWrite(void) {
    inTransaction = false;
    com_end();
}

/***************************************************************************************
** Function name:           writeColor (use startWrite() and endWrite() before & after)
** Description:             raw write of "len" pixels avoiding transaction check
***************************************************************************************/
void TFT_eSPI::writeColor(uint16_t color, uint32_t len) {
    #ifdef RPI_WRITE_STROBE
    uint8_t colorBin[] = { (uint8_t)(color >> 8), (uint8_t) color };
    if (len) {
        _com.writePattern(&colorBin[0], 2, 1);
    } len--;
    while (len--) {
        WR_L;
        WR_H;
    }
    #else
    writeBlock(color, len);
    #endif
}

/***************************************************************************************
** Function name:           pushColors
** Description:             push an array of pixels for 16 bit raw image drawing
***************************************************************************************/
// Assumed that setAddrWindow() has previously been called

void TFT_eSPI::pushColors(uint8_t* data, uint32_t len) {
    com_begin();

    #if defined (RPI_WRITE_STROBE)
    while (len >= 64) {
        _com.writePattern(data, 64, 1);
        data += 64;
        len -= 64;
    }
    if (len) {
        _com.transfer(data, len);
    }
    #else
    #if  defined (ILI9488_DRIVER)
    uint16_t color;
    while (len > 1) {
        color = (*data++);
        color |= ((*data++) << 8);
        tft_Write_16(color);
        len -= 2;
    }
    #else
    #if (SPI_FREQUENCY == 80000000)
    while (len >= 64) {
        _com.transfer(data, 64);
        data += 64;
        len -= 64;
    }
    if (len) {
        _com.transfer(data, len);
    }
    #else
    #if defined (__SAMD51__)
    _com.transfer(data, NULL, len, true);
    #else
    _com.transfer(data, len);
    #endif
    #endif
    #endif
    #endif

    com_end();
}


/***************************************************************************************
** Function name:           pushColors
** Description:             push an array of pixels, for image drawing
***************************************************************************************/
void TFT_eSPI::pushColors(uint16_t* data, uint32_t len, bool swap) {
    com_begin();

    #if defined (ILI9488_DRIVER)
    if (swap) while (len--) {
            tft_Write_16(*data);
            data++;
        } else while (len--) {
            tft_Write_16S(*data);
            data++;
        }
    #else
    #if defined (__SAMD51__)
    if (swap) {
        _com.transfer((void*)data, NULL, len << 1, true);
    } else {
        _com.transfer((void*)data, NULL, len << 1, true);
    }
    #else
    if (swap) {
        _com.transfer((void*)data, len << 1);
    } else {
        _com.transfer((void*)data, len << 1);
    }
    #endif

    #endif


    com_end();
}


/***************************************************************************************
** Function name:           drawLine
** Description:             draw a line between 2 arbitrary points
***************************************************************************************/
// Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer to use
// an efficient FastH/V Line draw routine for line segments of 2 pixels or more
void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) {
    //com_begin();          // Sprite class can use this function, avoiding com_begin()
    inTransaction = true;
    boolean steep = abs(y1 - y0) > abs(x1 - x0);
    if (steep) {
        swap_coord(x0, y0);
        swap_coord(x1, y1);
    }

    if (x0 > x1) {
        swap_coord(x0, x1);
        swap_coord(y0, y1);
    }

    int32_t dx = x1 - x0, dy = abs(y1 - y0);;

    int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;

    if (y0 < y1) {
        ystep = 1;
    }

    // Split into steep and not steep for FastH/V separation
    if (steep) {
        for (; x0 <= x1; x0++) {
            dlen++;
            err -= dy;
            if (err < 0) {
                err += dx;
                if (dlen == 1) {
                    drawPixel(y0, xs, color);
                } else {
                    drawFastVLine(y0, xs, dlen, color);
                }
                dlen = 0; y0 += ystep; xs = x0 + 1;
            }
        }
        if (dlen) {
            drawFastVLine(y0, xs, dlen, color);
        }
    } else {
        for (; x0 <= x1; x0++) {
            dlen++;
            err -= dy;
            if (err < 0) {
                err += dx;
                if (dlen == 1) {
                    drawPixel(xs, y0, color);
                } else {
                    drawFastHLine(xs, y0, dlen, color);
                }
                dlen = 0; y0 += ystep; xs = x0 + 1;
            }
        }
        if (dlen) {
            drawFastHLine(xs, y0, dlen, color);
        }
    }
    inTransaction = false;
    com_end();
}



/***************************************************************************************
** Function name:           drawFastVLine
** Description:             draw a vertical line
***************************************************************************************/
void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) {
    // Clipping
    if ((x < 0) || (x >= _width) || (y >= _height)) {
        return;
    }

    if (y < 0) {
        h += y;
        y = 0;
    }

    if ((y + h) > _height) {
        h = _height - y;
    }

    if (h < 1) {
        return;
    }

    com_begin();

    setWindow(x, y, x, y + h - 1);

    #ifdef RPI_WRITE_STROBE
    tft_Write_16(color);
    h--;
    while (h--) {
        WR_L;
        WR_H;
    }
    #else
    writeBlock(color, h);

    #endif

    com_end();
}

/***************************************************************************************
** Function name:           drawFastHLine
** Description:             draw a horizontal line
***************************************************************************************/
void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) {
    // Clipping
    if ((y < 0) || (x >= _width) || (y >= _height)) {
        return;
    }

    if (x < 0) {
        w += x;
        x = 0;
    }

    if ((x + w) > _width) {
        w = _width  - x;
    }

    if (w < 1) {
        return;
    }

    com_begin();

    setWindow(x, y, x + w - 1, y);

    #ifdef RPI_WRITE_STROBE
    tft_Write_16(color);
    w--;
    while (w--) {
        WR_L;
        WR_H;
    }
    #else
    writeBlock(color, w);
    #endif

    com_end();
}

/***************************************************************************************
** Function name:           fillRect
** Description:             draw a filled rectangle
***************************************************************************************/
void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {

    // Clipping
    if ((x >= _width) || (y >= _height)) {
        return;
    }

    if (x < 0) {
        w += x;
        x = 0;
    }
    if (y < 0) {
        h += y;
        y = 0;
    }

    if ((x + w) > _width) {
        w = _width  - x;
    }
    if ((y + h) > _height) {
        h = _height - y;
    }

    if ((w < 1) || (h < 1)) {
        return;
    }

    com_begin();

    setWindow(x, y, x + w - 1, y + h - 1);

    uint32_t n = (uint32_t)w * (uint32_t)h;

    #ifdef RPI_WRITE_STROBE
    tft_Write_16(color);
    while (n--) {
        WR_L;
        WR_H;
    }
    #else
    writeBlock(color, n);
    #endif

    com_end();
}

/***************************************************************************************
** Function name:           color565
** Description:             convert three 8 bit RGB levels to a 16 bit colour value
***************************************************************************************/
uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b) {
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}


/***************************************************************************************
** Function name:           color16to8
** Description:             convert 16 bit colour to an 8 bit 332 RGB colour value
***************************************************************************************/
uint8_t TFT_eSPI::color16to8(uint16_t c) {
    return ((c & 0xE000) >> 8) | ((c & 0x0700) >> 6) | ((c & 0x0018) >> 3);
}


/***************************************************************************************
** Function name:           color8to16
** Description:             convert 8 bit colour to a 16 bit 565 colour value
***************************************************************************************/
uint16_t TFT_eSPI::color8to16(uint8_t color) {
    uint8_t  blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
    uint16_t color16 = 0;

    //        =====Green=====     ===============Red==============
    color16  = (color & 0x1C) << 6 | (color & 0xC0) << 5 | (color & 0xE0) << 8;
    //        =====Green=====    =======Blue======
    color16 |= (color & 0x1C) << 3 | blue[color & 0x03];

    return color16;
}


/***************************************************************************************
** Function name:           invertDisplay
** Description:             invert the display colours i = 1 invert, i = 0 normal
***************************************************************************************/
void TFT_eSPI::invertDisplay(boolean i) {
    com_begin();
    // Send the command twice as otherwise it does not always work!
    writecommand(i ? TFT_INVON : TFT_INVOFF);
    writecommand(i ? TFT_INVON : TFT_INVOFF);
    com_end();
}


/**************************************************************************
** Function name:           setAttribute
** Description:             Sets a control parameter of an attribute
**************************************************************************/
void TFT_eSPI::setAttribute(uint8_t attr_id, uint8_t param) {
    switch (attr_id) {
            break;
        case 1:
            _cp437 = param;
            break;
        case 2:
            _utf8  = param;
            decoderState = 0;
            break;
            //case 3: // TBD future feature control
            //    _tbd = param;
            //    break;
    }
}


/**************************************************************************
** Function name:           getAttribute
** Description:             Get value of an attribute (control parameter)
**************************************************************************/
uint8_t TFT_eSPI::getAttribute(uint8_t attr_id) {
    switch (attr_id) {
        case 1: // ON/OFF control of full CP437 character set
            return _cp437;
            break;
        case 2: // ON/OFF control of UTF-8 decoding
            return _utf8;
            break;
            //case 3: // TBD future feature control
            //    return _tbd;
            //    break;
    }

    return false;
}

/***************************************************************************************
** Function name:           decodeUTF8
** Description:             Serial UTF-8 decoder with fall-back to extended ASCII
*************************************************************************************x*/
#define DECODE_UTF8 // Test only, comment out to stop decoding
uint16_t TFT_eSPI::decodeUTF8(uint8_t c) {
    #ifdef DECODE_UTF8
    // 7 bit Unicode Code Point
    if ((c & 0x80) == 0x00) {
        decoderState = 0;
        return (uint16_t)c;
    }

    if (decoderState == 0) {
        // 11 bit Unicode Code Point
        if ((c & 0xE0) == 0xC0) {
            decoderBuffer = ((c & 0x1F) << 6);
            decoderState = 1;
            return 0;
        }

        // 16 bit Unicode Code Point
        if ((c & 0xF0) == 0xE0) {
            decoderBuffer = ((c & 0x0F) << 12);
            decoderState = 2;
            return 0;
        }
        // 21 bit Unicode  Code Point not supported so fall-back to extended ASCII
        // if ((c & 0xF8) == 0xF0) return (uint16_t)c;
    } else {
        if (decoderState == 2) {
            decoderBuffer |= ((c & 0x3F) << 6);
            decoderState--;
            return 0;
        } else {
            decoderBuffer |= (c & 0x3F);
            decoderState = 0;
            return decoderBuffer;
        }
    }

    decoderState = 0;
    #endif

    return (uint16_t)c; // fall-back to extended ASCII
}


/***************************************************************************************
** Function name:           decodeUTF8
** Description:             Line buffer UTF-8 decoder with fall-back to extended ASCII
*************************************************************************************x*/
uint16_t TFT_eSPI::decodeUTF8(uint8_t* buf, uint16_t* index, uint16_t remaining) {
    uint16_t c = buf[(*index)++];
    //Serial.print("Byte from string = 0x"); Serial.println(c, HEX);

    #ifdef DECODE_UTF8
    // 7 bit Unicode
    if ((c & 0x80) == 0x00) {
        return c;
    }

    // 11 bit Unicode
    if (((c & 0xE0) == 0xC0) && (remaining > 1)) {
        return ((c & 0x1F) << 6) | (buf[(*index)++] & 0x3F);
    }

    // 16 bit Unicode
    if (((c & 0xF0) == 0xE0) && (remaining > 2)) {
        c = ((c & 0x0F) << 12) | ((buf[(*index)++] & 0x3F) << 6);
        return  c | ((buf[(*index)++] & 0x3F));
    }

    // 21 bit Unicode not supported so fall-back to extended ASCII
    // if ((c & 0xF8) == 0xF0) return c;
    #endif

    return c; // fall-back to extended ASCII
}


/***************************************************************************************
** Function name:           write
** Description:             draw characters piped through serial stream
***************************************************************************************/
size_t TFT_eSPI::write(uint8_t utf8) {
    if (utf8 == '\r') {
        return 1;
    }

    uint16_t uniCode = utf8;

    if (_utf8) {
        uniCode = decodeUTF8(utf8);
    }

    if (uniCode == 0) {
        return 1;
    }

    #ifdef SMOOTH_FONT
    if (fontLoaded) {
        //Serial.print("UniCode="); Serial.println(uniCode);
        //Serial.print("UTF8   ="); Serial.println(utf8);

        //fontFile = SPIFFS.open( _gFontFilename, "r" );

        //if(!fontFile)
        //{
        //  fontLoaded = false;
        //  return 1;
        //}

        drawGlyph(uniCode);

        //fontFile.close();
        return 1;
    }
    #endif

    if (uniCode == '\n') {
        uniCode += 22;    // Make it a valid space character to stop errors
    } else if (uniCode < 32) {
        return 1;
    }

    uint16_t width = 0;
    uint16_t height = 0;

    //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    //Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port
    //Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port
    //delay(5);                     // Debug optional wait for serial port to flush through
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    #ifdef LOAD_GFXFF
    if (!gfxFont) {
    #endif
        //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

        #ifdef LOAD_FONT2
        if (textfont == 2) {
            if (uniCode > 127) {
                return 1;
            }

            width = pgm_read_byte(widtbl_f16 + uniCode - 32);
            height = chr_hgt_f16;
            // Font 2 is rendered in whole byte widths so we must allow for this
            width = (width + 6) / 8;  // Width in whole bytes for font 2, should be + 7 but must allow for font width change
            width = width * 8;        // Width converted back to pixels
        }
        #ifdef LOAD_RLE
        else
        #endif
        #endif

        #ifdef LOAD_RLE
        {
            if ((textfont > 2) && (textfont < 9)) {
                if (uniCode > 127) {
                    return 1;
                }
                // Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
                width = pgm_read_byte((uint8_t*)pgm_read_dword(&(fontdata[textfont].widthtbl)) + uniCode - 32);
                height = pgm_read_byte(&fontdata[textfont].height);
            }
        }
        #endif

        #ifdef LOAD_GLCD
        if (textfont == 1) {
            width =  6;
            height = 8;
        }
        #else
        if (textfont == 1) {
            return 1;
        }
        #endif

        height = height * textsize;

        if (utf8 == '\n') {
            cursor_y += height;
            cursor_x  = 0;
        } else {
            if (textwrapX && (cursor_x + width * textsize > _width)) {
                cursor_y += height;
                cursor_x = 0;
            }
            if (textwrapY && (cursor_y >= (int32_t)_height)) {
                cursor_y = 0;
            }
            cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont);
        }

        //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        #ifdef LOAD_GFXFF
    } // Custom GFX font
    else {
        if (utf8 == '\n') {
            cursor_x  = 0;
            cursor_y += (int16_t)textsize *
                        (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
        } else {
            if (uniCode > pgm_read_word(&gfxFont->last)) {
                return 1;
            }
            if (uniCode < pgm_read_word(&gfxFont->first)) {
                return 1;
            }

            uint16_t   c2    = uniCode - pgm_read_word(&gfxFont->first);
            GFXglyph* glyph = &(((GFXglyph*)pgm_read_dword(&gfxFont->glyph))[c2]);
            uint8_t   w     = pgm_read_byte(&glyph->width),
                      h     = pgm_read_byte(&glyph->height);
            if ((w > 0) && (h > 0)) { // Is there an associated bitmap?
                int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
                if (textwrapX && ((cursor_x + textsize * (xo + w)) > _width)) {
                    // Drawing character would go off right edge; wrap to new line
                    cursor_x  = 0;
                    cursor_y += (int16_t)textsize *
                                (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
                }
                if (textwrapY && (cursor_y >= (int32_t)_height)) {
                    cursor_y = 0;
                }
                drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor, textsize);
            }
            cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize;
        }
    }
        #endif // LOAD_GFXFF
    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    return 1;
}


/***************************************************************************************
** Function name:           drawChar
** Description:             draw a Unicode glyph onto the screen
***************************************************************************************/
// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y) {
    return drawChar(uniCode, x, y, textfont);
}

// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) {
    if (!uniCode) {
        return 0;
    }

    if (font == 1) {
        #ifdef LOAD_GLCD
        #ifndef LOAD_GFXFF
        drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
        return 6 * textsize;
        #endif
        #else
        #ifndef LOAD_GFXFF
        return 0;
        #endif
        #endif

        #ifdef LOAD_GFXFF
        drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
        if (!gfxFont) { // 'Classic' built-in font
            #ifdef LOAD_GLCD
            return 6 * textsize;
            #else
            return 0;
            #endif
        } else {
            if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last))) {
                uint16_t   c2    = uniCode - pgm_read_word(&gfxFont->first);
                GFXglyph* glyph = &(((GFXglyph*)pgm_read_dword(&gfxFont->glyph))[c2]);
                return pgm_read_byte(&glyph->xAdvance) * textsize;
            } else {
                return 0;
            }
        }
        #endif
    }

    if ((font > 1) && (font < 9) && ((uniCode < 32) || (uniCode > 127))) {
        return 0;
    }

    int32_t width  = 0;
    int32_t height = 0;
    uint32_t flash_address = 0;
    uniCode -= 32;

    #ifdef LOAD_FONT2
    if (font == 2) {
        flash_address = pgm_read_dword(&chrtbl_f16[uniCode]);
        width = pgm_read_byte(widtbl_f16 + uniCode);
        height = chr_hgt_f16;
    }
    #ifdef LOAD_RLE
    else
    #endif
    #endif

    #ifdef LOAD_RLE
    {
        if ((font > 2) && (font < 9)) {
            flash_address = pgm_read_dword((const void*)(pgm_read_dword(&(fontdata[font].chartbl)) + uniCode * sizeof(void*)));
            width = pgm_read_byte((uint8_t*)pgm_read_dword(&(fontdata[font].widthtbl)) + uniCode);
            height = pgm_read_byte(&fontdata[font].height);
        }
    }
    #endif

    int32_t w = width;
    int32_t pX      = 0;
    int32_t pY      = y;
    uint8_t line = 0;

    #ifdef LOAD_FONT2 // chop out code if we do not need it
    if (font == 2) {
        w = w + 6; // Should be + 7 but we need to compensate for width increment
        w = w / 8;
        if (x + width * textsize >= (int16_t)_width) {
            return width * textsize ;
        }

        if (textcolor == textbgcolor || textsize != 1) {
            //com_begin();          // Sprite class can use this function, avoiding com_begin()
            inTransaction = true;

            for (int32_t i = 0; i < height; i++) {
                if (textcolor != textbgcolor) {
                    fillRect(x, pY, width * textsize, textsize, textbgcolor);
                }

                for (int32_t k = 0; k < w; k++) {
                    line = pgm_read_byte((uint8_t*)flash_address + w * i + k);
                    if (line) {
                        if (textsize == 1) {
                            pX = x + k * 8;
                            if (line & 0x80) {
                                drawPixel(pX, pY, textcolor);
                            }
                            if (line & 0x40) {
                                drawPixel(pX + 1, pY, textcolor);
                            }
                            if (line & 0x20) {
                                drawPixel(pX + 2, pY, textcolor);
                            }
                            if (line & 0x10) {
                                drawPixel(pX + 3, pY, textcolor);
                            }
                            if (line & 0x08) {
                                drawPixel(pX + 4, pY, textcolor);
                            }
                            if (line & 0x04) {
                                drawPixel(pX + 5, pY, textcolor);
                            }
                            if (line & 0x02) {
                                drawPixel(pX + 6, pY, textcolor);
                            }
                            if (line & 0x01) {
                                drawPixel(pX + 7, pY, textcolor);
                            }
                        } else {
                            pX = x + k * 8 * textsize;
                            if (line & 0x80) {
                                fillRect(pX, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x40) {
                                fillRect(pX + textsize, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x20) {
                                fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x10) {
                                fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x08) {
                                fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x04) {
                                fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x02) {
                                fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor);
                            }
                            if (line & 0x01) {
                                fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor);
                            }
                        }
                    }
                }
                pY += textsize;
            }

            inTransaction = false;
            com_end();
        } else
            // Faster drawing of characters and background using block write
        {
            com_begin();

            setWindow(x, y, x + width - 1, y + height - 1);

            uint8_t mask;
            for (int32_t i = 0; i < height; i++) {
                pX = width;
                for (int32_t k = 0; k < w; k++) {
                    line = pgm_read_byte((uint8_t*)(flash_address + w * i + k));
                    mask = 0x80;
                    while (mask && pX) {
                        if (line & mask) {
                            tft_Write_16(textcolor);
                        } else {
                            tft_Write_16(textbgcolor);
                        }
                        pX--;
                        mask = mask >> 1;
                    }
                }
                if (pX) {
                    tft_Write_16(textbgcolor);
                }
            }

            com_end();
        }
    }

    #ifdef LOAD_RLE
    else
    #endif
    #endif  //FONT2

    #ifdef LOAD_RLE  //674 bytes of code
        // Font is not 2 and hence is RLE encoded
    {
        com_begin();
        inTransaction = true;

        w *= height; // Now w is total number of pixels in the character
        if ((textsize != 1) || (textcolor == textbgcolor)) {
            if (textcolor != textbgcolor) {
                fillRect(x, pY, width * textsize, textsize * height, textbgcolor);
            }
            int32_t px = 0, py = pY; // To hold character block start and end column and row values
            int32_t pc = 0; // Pixel count
            uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel

            uint8_t tnp = 0; // Temporary copy of np for while loop
            uint8_t ts = textsize - 1; // Temporary copy of textsize
            // 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area
            // w is total number of pixels to plot to fill character block
            while (pc < w) {
                line = pgm_read_byte((uint8_t*)flash_address);
                flash_address++;
                if (line & 0x80) {
                    line &= 0x7F;
                    line++;
                    if (ts) {
                        px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow
                        py = y + textsize * (pc / width);
                    } else {
                        px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow
                        py = y + pc / width;
                    }
                    while (line--) { // In this case the while(line--) is faster
                        pc++; // This is faster than putting pc+=line before while()?
                        setWindow(px, py, px + ts, py + ts);

                        if (ts) {
                            tnp = np;
                            while (tnp--) {
                                tft_Write_16(textcolor);
                            }
                        } else {
                            tft_Write_16(textcolor);
                        }
                        px += textsize;

                        if (px >= (x + width * textsize)) {
                            px = x;
                            py += textsize;
                        }
                    }
                } else {
                    line++;
                    pc += line;
                }
            }
        } else // Text colour != background && textsize = 1
            // so use faster drawing of characters and background using block write
        {
            setWindow(x, y, x + width - 1, y + height - 1);

            #ifdef RPI_WRITE_STROBE
            uint8_t textcolorBin[] = { (uint8_t)(textcolor >> 8), (uint8_t) textcolor };
            uint8_t textbgcolorBin[] = { (uint8_t)(textbgcolor >> 8), (uint8_t) textbgcolor };
            #endif

            // Maximum font size is equivalent to 180x180 pixels in area
            while (w > 0) {
                line = pgm_read_byte((uint8_t*)flash_address++);  // 8 bytes smaller when incrementing here
                if (line & 0x80) {
                    line &= 0x7F;
                    line++; w -= line;
                    #ifdef RPI_WRITE_STROBE
                    _com.transfer(&textcolorBin[0], 2); line--;
                    while (line--) {
                        WR_L;
                        WR_H;
                    }
                    #else
                    writeBlock(textcolor, line);
                    #endif
                } else {
                    line++; w -= line;
                    #ifdef RPI_WRITE_STROBE
                    _com.transfer(&textbgcolorBin[0], 2); line--;
                    while (line--) {
                        WR_L;
                        WR_H;
                    }
                    #else
                    writeBlock(textbgcolor, line);
                    #endif
                }
            }
        }
        inTransaction = false;
        com_end();
    }
    // End of RLE font rendering
    #endif
    return width * textsize;    // x +
}


/***************************************************************************************
** Function name:           drawString (with or without user defined font)
** Description :            draw string with padding if it is defined
***************************************************************************************/
// Without font number, uses font set by setTextFont()
int16_t TFT_eSPI::drawString(const String& string, int32_t poX, int32_t poY) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawString(buffer, poX, poY, textfont);
}
// With font number
int16_t TFT_eSPI::drawString(const String& string, int32_t poX, int32_t poY, uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawString(buffer, poX, poY, font);
}

// Without font number, uses font set by setTextFont()
int16_t TFT_eSPI::drawString(const char* string, int32_t poX, int32_t poY) {
    return drawString(string, poX, poY, textfont);
}

// With font number. Note: font number is over-ridden if a smooth font is loaded
int16_t TFT_eSPI::drawString(const char* string, int32_t poX, int32_t poY, uint8_t font) {
    int16_t sumX = 0;
    uint8_t padding = 1, baseline = 0;
    uint16_t cwidth = textWidth(string, font); // Find the pixel width of the string in the font
    uint16_t cheight = 8 * textsize;

    #ifdef LOAD_GFXFF
    #ifdef SMOOTH_FONT
    bool freeFont = (font == 1 && gfxFont && !fontLoaded);
    #else
    bool freeFont = (font == 1 && gfxFont);
    #endif

    if (freeFont) {
        cheight = glyph_ab * textsize;
        poY += cheight; // Adjust for baseline datum of free fonts
        baseline = cheight;
        padding = 101; // Different padding method used for Free Fonts

        // We need to make an adjustment for the bottom of the string (eg 'y' character)
        if ((textdatum == BL_DATUM) || (textdatum == BC_DATUM) || (textdatum == BR_DATUM)) {
            cheight += glyph_bb * textsize;
        }
    }
    #endif


    // If it is not font 1 (GLCD or free font) get the baseline and pixel height of the font
    #ifdef SMOOTH_FONT
    if (fontLoaded) {
        baseline = gFont.maxAscent;
        cheight  = fontHeight();
    } else
    #endif
        if (font != 1) {
            baseline = pgm_read_byte(&fontdata[font].baseline) * textsize;
            cheight = fontHeight(font);
        }

    if (textdatum || padX) {

        switch (textdatum) {
            case TC_DATUM:
                poX -= cwidth / 2;
                padding += 1;
                break;
            case TR_DATUM:
                poX -= cwidth;
                padding += 2;
                break;
            case ML_DATUM:
                poY -= cheight / 2;
                //padding += 0;
                break;
            case MC_DATUM:
                poX -= cwidth / 2;
                poY -= cheight / 2;
                padding += 1;
                break;
            case MR_DATUM:
                poX -= cwidth;
                poY -= cheight / 2;
                padding += 2;
                break;
            case BL_DATUM:
                poY -= cheight;
                //padding += 0;
                break;
            case BC_DATUM:
                poX -= cwidth / 2;
                poY -= cheight;
                padding += 1;
                break;
            case BR_DATUM:
                poX -= cwidth;
                poY -= cheight;
                padding += 2;
                break;
            case L_BASELINE:
                poY -= baseline;
                //padding += 0;
                break;
            case C_BASELINE:
                poX -= cwidth / 2;
                poY -= baseline;
                padding += 1;
                break;
            case R_BASELINE:
                poX -= cwidth;
                poY -= baseline;
                padding += 2;
                break;
        }
        // Check coordinates are OK, adjust if not
        if (poX < 0) {
            poX = 0;
        }
        if (poX + cwidth > width()) {
            poX = width() - cwidth;
        }
        if (poY < 0) {
            poY = 0;
        }
        if (poY + cheight - baseline > height()) {
            poY = height() - cheight;
        }
    }


    int8_t xo = 0;
    #ifdef LOAD_GFXFF
    if (freeFont && (textcolor != textbgcolor)) {
        cheight = (glyph_ab + glyph_bb) * textsize;
        // Get the offset for the first character only to allow for negative offsets
        uint16_t c2 = 0;
        uint16_t len = strlen(string);
        uint16_t n = 0;

        while (n < len && c2 == 0) {
            c2 = decodeUTF8((uint8_t*)string, &n, len - n);
        }

        if ((c2 >= pgm_read_word(&gfxFont->first)) && (c2 <= pgm_read_word(&gfxFont->last))) {
            c2 -= pgm_read_word(&gfxFont->first);
            GFXglyph* glyph = &(((GFXglyph*)pgm_read_dword(&gfxFont->glyph))[c2]);
            xo = pgm_read_byte(&glyph->xOffset) * textsize;
            // Adjust for negative xOffset
            if (xo > 0) {
                xo = 0;
            } else {
                cwidth -= xo;
            }
            // Add 1 pixel of padding all round
            //cheight +=2;
            //fillRect(poX+xo-1, poY - 1 - glyph_ab * textsize, cwidth+2, cheight, textbgcolor);
            fillRect(poX + xo, poY - glyph_ab * textsize, cwidth, cheight, textbgcolor);
        }
        padding -= 100;
    }
    #endif

    uint16_t len = strlen(string);
    uint16_t n = 0;

    #ifdef SMOOTH_FONT
    if (fontLoaded) {
        if (textcolor != textbgcolor) {
            fillRect(poX, poY, cwidth, cheight, textbgcolor);
        }
        //drawLine(poX - 5, poY, poX + 5, poY, TFT_GREEN);
        //drawLine(poX, poY - 5, poX, poY + 5, TFT_GREEN);
        //fontFile = SPIFFS.open( _gFontFilename, "r");
        if (!fontFile) {
            return 0;
        }

        setCursor(poX, poY);

        while (n < len) {
            uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
            drawGlyph(uniCode);
        }
        sumX += cwidth;
        //fontFile.close();
    } else
    #endif
    {
        while (n < len) {
            uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
            sumX += drawChar(uniCode, poX + sumX, poY, font);
        }
    }

    //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    // Switch on debugging for the padding areas
    //#define PADDING_DEBUG

    #ifndef PADDING_DEBUG
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    if ((padX > cwidth) && (textcolor != textbgcolor)) {
        int16_t padXc = poX + cwidth + xo;
        #ifdef LOAD_GFXFF
        if (freeFont) {
            poX += xo; // Adjust for negative offset start character
            poY -= glyph_ab * textsize;
            sumX += poX;
        }
        #endif
        switch (padding) {
            case 1:
                fillRect(padXc, poY, padX - cwidth, cheight, textbgcolor);
                break;
            case 2:
                fillRect(padXc, poY, (padX - cwidth) >> 1, cheight, textbgcolor);
                padXc = (padX - cwidth) >> 1;
                if (padXc > poX) {
                    padXc = poX;
                }
                fillRect(poX - padXc, poY, (padX - cwidth) >> 1, cheight, textbgcolor);
                break;
            case 3:
                if (padXc > padX) {
                    padXc = padX;
                }
                fillRect(poX + cwidth - padXc, poY, padXc - cwidth, cheight, textbgcolor);
                break;
        }
    }


    #else

    //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    // This is debug code to show text (green box) and blanked (white box) areas
    // It shows that the padding areas are being correctly sized and positioned

    if ((padX > sumX) && (textcolor != textbgcolor)) {
        int16_t padXc = poX + sumX; // Maximum left side padding
        #ifdef LOAD_GFXFF
        if ((font == 1) && (gfxFont)) {
            poY -= glyph_ab;
        }
        #endif
        drawRect(poX, poY, sumX, cheight, TFT_GREEN);
        switch (padding) {
            case 1:
                drawRect(padXc, poY, padX - sumX, cheight, TFT_WHITE);
                break;
            case 2:
                drawRect(padXc, poY, (padX - sumX) >> 1, cheight, TFT_WHITE);
                padXc = (padX - sumX) >> 1;
                if (padXc > poX) {
                    padXc = poX;
                }
                drawRect(poX - padXc, poY, (padX - sumX) >> 1, cheight, TFT_WHITE);
                break;
            case 3:
                if (padXc > padX) {
                    padXc = padX;
                }
                drawRect(poX + sumX - padXc, poY, padXc - sumX, cheight, TFT_WHITE);
                break;
        }
    }
    #endif
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    return sumX;
}


/***************************************************************************************
** Function name:           drawCentreString (deprecated, use setTextDatum())
** Descriptions:            draw string centred on dX
***************************************************************************************/
int16_t TFT_eSPI::drawCentreString(const String& string, int32_t dX, int32_t poY, uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawCentreString(buffer, dX, poY, font);
}

int16_t TFT_eSPI::drawCentreString(const char* string, int32_t dX, int32_t poY, uint8_t font) {
    uint8_t tempdatum = textdatum;
    int32_t sumX = 0;
    textdatum = TC_DATUM;
    sumX = drawString(string, dX, poY, font);
    textdatum = tempdatum;
    return sumX;
}


/***************************************************************************************
** Function name:           drawRightString (deprecated, use setTextDatum())
** Descriptions:            draw string right justified to dX
***************************************************************************************/
int16_t TFT_eSPI::drawRightString(const String& string, int32_t dX, int32_t poY, uint8_t font) {
    int16_t len = string.length() + 2;
    char buffer[len];
    string.toCharArray(buffer, len);
    return drawRightString(buffer, dX, poY, font);
}

int16_t TFT_eSPI::drawRightString(const char* string, int32_t dX, int32_t poY, uint8_t font) {
    uint8_t tempdatum = textdatum;
    int16_t sumX = 0;
    textdatum = TR_DATUM;
    sumX = drawString(string, dX, poY, font);
    textdatum = tempdatum;
    return sumX;
}


/***************************************************************************************
** Function name:           drawNumber
** Description:             draw a long integer
***************************************************************************************/
int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY) {
    isDigits = true; // Eliminate jiggle in monospaced fonts
    char str[12];
    utoa(long_num, str, 10);
    return drawString(str, poX, poY, textfont);
}

int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY, uint8_t font) {
    isDigits = true; // Eliminate jiggle in monospaced fonts
    char str[12];
    utoa(long_num, str, 10);
    return drawString(str, poX, poY, font);
}


/***************************************************************************************
** Function name:           drawFloat
** Descriptions:            drawFloat, prints 7 non zero digits maximum
***************************************************************************************/
// Assemble and print a string, this permits alignment relative to a datum
// looks complicated but much more compact and actually faster than using print class
int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX, int32_t poY) {
    return drawFloat(floatNumber, dp, poX, poY, textfont);
}

int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX, int32_t poY, uint8_t font) {
    isDigits = true;
    char str[14];               // Array to contain decimal string
    uint8_t ptr = 0;            // Initialise pointer for array
    int8_t  digits = 1;         // Count the digits to avoid array overflow
    float rounding = 0.5;       // Round up down delta

    if (dp > 7) {
        dp = 7;    // Limit the size of decimal portion
    }

    // Adjust the rounding value
    for (uint8_t i = 0; i < dp; ++i) {
        rounding /= 10.0;
    }

    if (floatNumber < -rounding) {  // add sign, avoid adding - sign to 0.0!
        str[ptr++] = '-'; // Negative number
        str[ptr] = 0; // Put a null in the array as a precaution
        digits = 0;   // Set digits to 0 to compensate so pointer value can be used later
        floatNumber = -floatNumber; // Make positive
    }

    floatNumber += rounding; // Round up or down

    // For error put ... in string and return (all TFT_eSPI library fonts contain . character)
    if (floatNumber >= 2147483647) {
        strcpy(str, "...");
        return drawString(str, poX, poY, font);
    }
    // No chance of overflow from here on

    // Get integer part
    uint32_t temp = (uint32_t)floatNumber;

    // Put integer part into array
    utoa(temp, str + ptr, 10);

    // Find out where the null is to get the digit count loaded
    while ((uint8_t)str[ptr] != 0) {
        ptr++;    // Move the pointer along
    }
    digits += ptr;                  // Count the digits

    str[ptr++] = '.'; // Add decimal point
    str[ptr] = '0';   // Add a dummy zero
    str[ptr + 1] = 0; // Add a null but don't increment pointer so it can be overwritten

    // Get the decimal portion
    floatNumber = floatNumber - temp;

    // Get decimal digits one by one and put in array
    // Limit digit count so we don't get a false sense of resolution
    uint8_t i = 0;
    while ((i < dp) && (digits < 9)) { // while (i < dp) for no limit but array size must be increased
        i++;
        floatNumber *= 10;       // for the next decimal
        temp = floatNumber;      // get the decimal
        utoa(temp, str + ptr, 10);
        ptr++; digits++;         // Increment pointer and digits count
        floatNumber -= temp;     // Remove that digit
    }

    // Finally we can plot the string and return pixel length
    return drawString(str, poX, poY, font);
}


/***************************************************************************************
** Function name:           setFreeFont
** Descriptions:            Sets the GFX free font to use
***************************************************************************************/

#ifdef LOAD_GFXFF

void TFT_eSPI::setFreeFont(const GFXfont* f) {
    if (f == nullptr) {
        setTextFont(1); // Use GLCD font
        return;
    }

    textfont = 1;
    gfxFont = (GFXfont*)f;

    glyph_ab = 0;
    glyph_bb = 0;
    uint16_t numChars = pgm_read_word(&gfxFont->last) - pgm_read_word(&gfxFont->first);

    // Find the biggest above and below baseline offsets
    for (uint8_t c = 0; c < numChars; c++) {
        GFXglyph* glyph1  = &(((GFXglyph*)pgm_read_dword(&gfxFont->glyph))[c]);
        int8_t ab = -pgm_read_byte(&glyph1->yOffset);
        if (ab > glyph_ab) {
            glyph_ab = ab;
        }
        int8_t bb = pgm_read_byte(&glyph1->height) - ab;
        if (bb > glyph_bb) {
            glyph_bb = bb;
        }
    }
}


/***************************************************************************************
** Function name:           setTextFont
** Description:             Set the font for the print stream
***************************************************************************************/
void TFT_eSPI::setTextFont(uint8_t f) {
    textfont = (f > 0) ? f : 1; // Don't allow font 0
    gfxFont = NULL;
}

#else


/***************************************************************************************
** Function name:           setFreeFont
** Descriptions:            Sets the GFX free font to use
***************************************************************************************/

// Alternative to setTextFont() so we don't need two different named functions
void TFT_eSPI::setFreeFont(uint8_t font) {
    setTextFont(font);
}


/***************************************************************************************
** Function name:           setTextFont
** Description:             Set the font for the print stream
***************************************************************************************/
void TFT_eSPI::setTextFont(uint8_t f) {
    textfont = (f > 0) ? f : 1; // Don't allow font 0
}

#endif


/***************************************************************************************
** Function name:           writeBlock
** Description:             Write a block of pixels of the same colour
***************************************************************************************/
//Clear screen test 76.8ms theoretical. 81.5ms TFT_eSPI, 967ms Adafruit_ILI9341
//Performance 26.15Mbps@26.66MHz, 39.04Mbps@40MHz, 75.4Mbps@80MHz SPI clock
//Efficiency:
//       TFT_eSPI       98.06%              97.59%          94.24%
//       Adafruit_GFX   19.62%              14.31%           7.94%
//
#if defined (ILI9488_DRIVER)
void writeBlock(uint16_t color, uint32_t repeat) {
    // Split out the colours
    uint32_t r = (color & 0xF800) >> 8;
    uint32_t g = (color & 0x07E0) << 5;
    uint32_t b = (color & 0x001F) << 19;
    // Concatenate 4 pixels into three 32 bit blocks
    uint32_t r0 = r << 24 | b | g | r;
    uint32_t r1 = r0 >> 8 | g << 16;
    uint32_t r2 = r1 >> 8 | b << 8;
    while (repeat) {
        tft_Write_32(r0);
        tft_Write_32(r1);
        tft_Write_32(r2) repet -= 3
    };
}
#else

void writeBlock(uint16_t color, uint32_t repeat) {
    switch (repeat & 0x7) {
        case 1: repeat &= ~0x07; goto out1;
        case 2: repeat &= ~0x07; goto out2;
        case 3: repeat &= ~0x07; goto out3;
        case 4: repeat &= ~0x07; goto out4;
        case 5: repeat &= ~0x07; goto out5;
        case 6: repeat &= ~0x07; goto out6;
        case 7: repeat &= ~0x07; goto out7;

        default: break;
    }

    while (repeat > 0) {
        repeat -= 8;

    out8: _com.transfer16(color);
    out7: _com.transfer16(color);
    out6: _com.transfer16(color);
    out5: _com.transfer16(color);
    out4: _com.transfer16(color);
    out3: _com.transfer16(color);
    out2: _com.transfer16(color);
    out1: _com.transfer16(color);
    }
}
#endif

/***************************************************************************************
** Function name:           getSetup
** Description:             Get the setup details for diagnostic and sketch access
***************************************************************************************/
void TFT_eSPI::getSetup(setup_t& tft_settings) {
    // tft_settings.version is set in header file
    tft_settings.esp = -1;

    #if defined (SUPPORT_TRANSACTIONS)
    tft_settings.trans = true;
    #else
    tft_settings.trans = false;
    #endif

    #if defined(TFT_SPI_OVERLAP)
    tft_settings.overlap = true;
    #else
    tft_settings.overlap = false;
    #endif

    tft_settings.tft_driver = TFT_DRIVER;
    tft_settings.tft_width  = _init_width;
    tft_settings.tft_height = _init_height;

    #ifdef CGRAM_OFFSET
    tft_settings.r0_x_offset = colstart;
    tft_settings.r0_y_offset = rowstart;
    tft_settings.r1_x_offset = 0;
    tft_settings.r1_y_offset = 0;
    tft_settings.r2_x_offset = 0;
    tft_settings.r2_y_offset = 0;
    tft_settings.r3_x_offset = 0;
    tft_settings.r3_y_offset = 0;
    #else
    tft_settings.r0_x_offset = 0;
    tft_settings.r0_y_offset = 0;
    tft_settings.r1_x_offset = 0;
    tft_settings.r1_y_offset = 0;
    tft_settings.r2_x_offset = 0;
    tft_settings.r2_y_offset = 0;
    tft_settings.r3_x_offset = 0;
    tft_settings.r3_y_offset = 0;
    #endif

    #if defined (TFT_MOSI)
    tft_settings.pin_tft_mosi = TFT_MOSI;
    #else
    tft_settings.pin_tft_mosi = -1;
    #endif

    #if defined (TFT_MISO)
    tft_settings.pin_tft_miso = TFT_MISO;
    #else
    tft_settings.pin_tft_miso = -1;
    #endif

    #if defined (TFT_SCLK)
    tft_settings.pin_tft_clk  = TFT_SCLK;
    #else
    tft_settings.pin_tft_clk  = -1;
    #endif

    #if defined (TFT_CS)
    tft_settings.pin_tft_cs   = TFT_CS;
    #else
    tft_settings.pin_tft_cs   = -1;
    #endif

    #if defined (TFT_DC)
    tft_settings.pin_tft_dc  = TFT_DC;
    #else
    tft_settings.pin_tft_dc  = -1;
    #endif

    #if defined (TFT_RD)
    tft_settings.pin_tft_rd  = TFT_RD;
    #else
    tft_settings.pin_tft_rd  = -1;
    #endif

    #if defined (TFT_WR)
    tft_settings.pin_tft_wr  = TFT_WR;
    #else
    tft_settings.pin_tft_wr  = -1;
    #endif

    #if defined (TFT_RST)
    tft_settings.pin_tft_rst = TFT_RST;
    #else
    tft_settings.pin_tft_rst = -1;
    #endif

    #if defined (TOUCH_CS)
    tft_settings.pin_tch_cs   = TOUCH_CS;
    tft_settings.tch_spi_freq = SPI_TOUCH_FREQUENCY / 100000;
    #else
    tft_settings.pin_tch_cs   = -1;
    tft_settings.tch_spi_freq = 0;
    #endif
}

////////////////////////////////////////////////////////////////////////////////////////
#ifdef TOUCH
    #include "Extensions/Touch.cpp"
    #include "Extensions/Button.cpp"
#endif

#include "Extensions/Sprite.cpp"

#ifdef SMOOTH_FONT
    #include "Extensions/Smooth_font.cpp"
#endif
////////////////////////////////////////////////////////////////////////////////////////

